diff --git a/usr.bin/netstat/common.h b/usr.bin/netstat/common.h index d5aadd50b34e..113180c0a6f4 100644 --- a/usr.bin/netstat/common.h +++ b/usr.bin/netstat/common.h @@ -1,92 +1,93 @@ /*- * 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. * * @(#)netstat.h 8.2 (Berkeley) 1/4/94 * $FreeBSD$ */ #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/route.c b/usr.bin/netstat/route.c index 0026ee924854..5de683ebfe7a 100644 --- a/usr.bin/netstat/route.c +++ b/usr.bin/netstat/route.c @@ -1,726 +1,727 @@ /*- * 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. */ #if 0 #ifndef lint static char sccsid[] = "From: @(#)route.c 8.6 (Berkeley) 4/28/95"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 } }; static struct ifmap_entry *ifmap; static size_t ifmap_size; 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 static void p_rtable_sysctl(int, int); static void p_rtentry_sysctl(const char *name, struct rt_msghdr *); 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) 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) 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"); if (!p_rtable_netlink(fibnum, af)) p_rtable_sysctl(fibnum, af); 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 */ #ifndef INET6 #define WID_DST_DEFAULT(af) 18 /* width of destination column */ #define WID_GW_DEFAULT(af) 18 /* width of gateway column */ #define WID_IF_DEFAULT(af) (Wflag ? 10 : 8) /* width of netif 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) #define WID_IF_DEFAULT(af) ((af) == AF_INET6 ? 8 : (Wflag ? 10 : 8)) #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(fam); wid.expire = 6; } 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) err(EX_OSERR, "sysctl: net.route.0.%d.dump.%d estimate", af, fibnum); if ((buf = malloc(needed)) == NULL) errx(2, "malloc(%lu)", (unsigned long)needed); if (sysctl(mib, nitems(mib), buf, &needed, NULL, 0) < 0) err(1, "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); } 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 (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 (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; u_long rtsaddr; if ((rtsaddr = nl[N_RTSTAT].n_value) == 0) { xo_emit("{W:rtstat: symbol not in namelist}\n"); return; } kread_counters(rtsaddr, (char *)&rtstat, sizeof (rtstat)); 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 index e4a56af4b8a9..17b15579b83b 100644 --- a/usr.bin/netstat/route_netlink.c +++ b/usr.bin/netstat/route_netlink.c @@ -1,491 +1,503 @@ /*- * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; struct nl_parsed_link { uint32_t ifi_index; uint32_t ifla_mtu; char *ifla_ifname; }; #define _IN(_field) offsetof(struct ifinfomsg, _field) #define _OUT(_field) offsetof(struct nl_parsed_link, _field) static struct snl_attr_parser ap_link[] = { { .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string }, { .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 }, }; static struct snl_field_parser fp_link[] = { {.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link); /* 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(ss, &msg, sizeof(msg))) { snl_free(ss); return (NULL); } struct ifmap_entry *ifmap = NULL; uint32_t ifmap_size = 0; struct nlmsghdr *hdr; while ((hdr = snl_read_message(ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) { if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq) continue; /* if (hdr->nlmsg_type == NLMSG_ERROR) break; */ struct nl_parsed_link link = {}; if (!snl_parse_nlmsg(ss, hdr, &link_parser, &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) errx(2, "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); } struct rta_mpath_nh { struct sockaddr *gw; uint32_t ifindex; uint8_t rtnh_flags; uint8_t rtnh_weight; uint32_t rtax_mtu; - uint32_t rta_knh_id; uint32_t rta_rtflags; }; #define _IN(_field) offsetof(struct rtnexthop, _field) #define _OUT(_field) offsetof(struct rta_mpath_nh, _field) static const struct snl_attr_parser nla_p_mp_rtmetrics[] = { { .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_ATTR_PARSER(metrics_mp_parser, nla_p_mp_rtmetrics); static const struct snl_attr_parser psnh[] = { { .type = NL_RTA_GATEWAY, .off = _OUT(gw), .cb = snl_attr_get_ip }, { .type = NL_RTA_METRICS, .arg = &metrics_mp_parser, .cb = snl_attr_get_nested }, - { .type = NL_RTA_KNH_ID, .off = _OUT(rta_knh_id), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_RTFLAGS, .off = _OUT(gw), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_VIA, .off = _OUT(gw), .cb = snl_attr_get_ipvia }, }; static const struct snl_field_parser fpnh[] = { { .off_in = _IN(rtnh_flags), .off_out = _OUT(rtnh_flags), .cb = snl_field_get_uint8 }, { .off_in = _IN(rtnh_hops), .off_out = _OUT(rtnh_weight), .cb = snl_field_get_uint8 }, { .off_in = _IN(rtnh_ifindex), .off_out = _OUT(ifindex), .cb = snl_field_get_uint32 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(mpath_parser, struct rtnexthop, fpnh, psnh); struct rta_mpath { int num_nhops; struct rta_mpath_nh nhops[0]; }; static bool nlattr_get_multipath(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) { int data_len = nla->nla_len - sizeof(struct nlattr); struct rtnexthop *rtnh; int max_nhops = data_len / sizeof(struct rtnexthop); size_t sz = (max_nhops + 2) * sizeof(struct rta_mpath_nh); struct rta_mpath *mp = snl_allocz(ss, sz); mp->num_nhops = 0; for (rtnh = (struct rtnexthop *)(nla + 1); data_len > 0; ) { struct rta_mpath_nh *mpnh = &mp->nhops[mp->num_nhops++]; if (!snl_parse_header(ss, rtnh, rtnh->rtnh_len, &mpath_parser, mpnh)) return (false); int len = NL_ITEM_ALIGN(rtnh->rtnh_len); data_len -= len; rtnh = (struct rtnexthop *)((char *)rtnh + len); } if (data_len != 0 || mp->num_nhops == 0) { return (false); } *((struct rta_mpath **)target) = mp; return (true); } struct nl_parsed_route { struct sockaddr *rta_dst; struct sockaddr *rta_gw; struct nlattr *rta_metrics; struct rta_mpath *rta_multipath; uint32_t rta_expires; uint32_t rta_oif; uint32_t rta_expire; uint32_t rta_table; uint32_t rta_knh_id; + uint32_t rta_nh_id; uint32_t rta_rtflags; uint32_t rtax_mtu; uint32_t rtax_weight; uint8_t rtm_family; uint8_t rtm_type; uint8_t rtm_protocol; uint8_t rtm_dst_len; }; #define _IN(_field) offsetof(struct rtmsg, _field) #define _OUT(_field) offsetof(struct nl_parsed_route, _field) static const struct snl_attr_parser nla_p_rtmetrics[] = { { .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_ATTR_PARSER(metrics_parser, nla_p_rtmetrics); static const struct snl_attr_parser ps[] = { { .type = NL_RTA_DST, .off = _OUT(rta_dst), .cb = snl_attr_get_ip }, { .type = NL_RTA_OIF, .off = _OUT(rta_oif), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_GATEWAY, .off = _OUT(rta_gw), .cb = snl_attr_get_ip }, { .type = NL_RTA_METRICS, .arg = &metrics_parser, .cb = snl_attr_get_nested }, { .type = NL_RTA_MULTIPATH, .off = _OUT(rta_multipath), .cb = nlattr_get_multipath }, { .type = NL_RTA_KNH_ID, .off = _OUT(rta_knh_id), .cb = snl_attr_get_uint32 }, + { .type = NL_RTA_WEIGHT, .off = _OUT(rtax_weight), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_RTFLAGS, .off = _OUT(rta_rtflags), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_TABLE, .off = _OUT(rta_table), .cb = snl_attr_get_uint32 }, { .type = NL_RTA_VIA, .off = _OUT(rta_gw), .cb = snl_attr_get_ipvia }, { .type = NL_RTA_EXPIRES, .off = _OUT(rta_expire), .cb = snl_attr_get_uint32 }, + { .type = NL_RTA_NH_ID, .off = _OUT(rta_nh_id), .cb = snl_attr_get_uint32 }, }; static const struct snl_field_parser fprt[] = { {.off_in = _IN(rtm_family), .off_out = _OUT(rtm_family), .cb = snl_field_get_uint8 }, {.off_in = _IN(rtm_type), .off_out = _OUT(rtm_type), .cb = snl_field_get_uint8 }, {.off_in = _IN(rtm_protocol), .off_out = _OUT(rtm_protocol), .cb = snl_field_get_uint8 }, {.off_in = _IN(rtm_dst_len), .off_out = _OUT(rtm_dst_len), .cb = snl_field_get_uint8 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(rtm_parser, struct rtmsg, fprt, ps); #define RTF_UP 0x1 #define RTF_GATEWAY 0x2 #define RTF_HOST 0x4 #define RTF_REJECT 0x8 #define RTF_DYNAMIC 0x10 #define RTF_STATIC 0x800 #define RTF_BLACKHOLE 0x1000 #define RTF_PROTO2 0x4000 #define RTF_PROTO1 0x8000 #define RTF_PROTO3 0x40000 #define RTF_FIXEDMTU 0x80000 #define RTF_PINNED 0x100000 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; } } struct sockaddr_dl_short { u_char sdl_len; /* Total length of sockaddr */ u_char sdl_family; /* AF_LINK */ u_short sdl_index; /* if != 0, system given index for interface */ u_char sdl_type; /* interface type */ u_char sdl_nlen; /* interface name length, no trailing 0 reqd. */ u_char sdl_alen; /* link level address length */ u_char sdl_slen; /* link layer selector length */ char sdl_data[8]; /* unused */ }; static void -p_path(struct nl_parsed_route *rt) +p_path(struct nl_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); 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, rt->rta_knh_id); + 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 nl_parsed_route rt = {}; if (!snl_parse_nlmsg(ss, hdr, &rtm_parser, &rt)) return; + if (rt.rtax_weight == 0) + rt.rtax_weight = rt_default_weight; if (rt.rta_multipath != NULL) { uint32_t orig_rtflags = rt.rta_rtflags; uint32_t orig_mtu = rt.rtax_mtu; for (int 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.rta_knh_id = nhop->rta_knh_id; rt.rtax_mtu = nhop->rtax_mtu ? nhop->rtax_mtu : orig_mtu; xo_open_instance(name); - p_path(&rt); + p_path(&rt, true); xo_emit("\n"); xo_close_instance(name); } return; } struct sockaddr_dl_short sdl_gw = { .sdl_family = AF_LINK, .sdl_len = sizeof(struct sockaddr_dl_short), .sdl_index = rt.rta_oif, }; if (rt.rta_gw == NULL) rt.rta_gw = (struct sockaddr *)&sdl_gw; xo_open_instance(name); - p_path(&rt); + p_path(&rt, false); xo_emit("\n"); xo_close_instance(name); } static const struct snl_hdr_parser *all_parsers[] = { &link_parser, &metrics_mp_parser, &mpath_parser, &metrics_parser, &rtm_parser }; bool p_rtable_netlink(int fibnum, int af) { int fam = AF_UNSPEC; int need_table_close = false; struct nlmsghdr *hdr; struct snl_state ss = {}; SNL_VERIFY_PARSERS(all_parsers); if (!snl_init(&ss, NETLINK_ROUTE)) return (false); ifmap = prepare_ifmap_netlink(&ss, &ifmap_size); 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(&ss, &msg, sizeof(msg))) { snl_free(&ss); return (false); } xo_open_container("route-table"); xo_open_list("rt-family"); while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) { if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq) continue; 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); }