From nobody Fri Jan 27 15:36:19 2023 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4P3M9w0TkKz3cFJ5; Fri, 27 Jan 2023 15:36:20 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4P3M9v72HKz3PRW; Fri, 27 Jan 2023 15:36:19 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1674833780; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Al4OaKu7vFhMp7A4vZrcnoaIwYRn4dQmCYM4rdMXs04=; b=mbP8peRMMTVTuwTcl22b2SN341WBAoQRpqAwdsL3FyQjl/OC0hx6Fdm8n6aWtqvqQr9ZQI MabwrBx8svjnwLdoT/30vrtz+ZLJ1Z605XhhJVeBvvx1Jl+EM4iUKSPCl6YYcWzqAhPzRM DLHGOM+hOyEJruge7W04XiBJqOPQAHEBciviNWIOFE3NdP0DD0pL0g08yE5rtzdI5Uv2Yu i13xLt3QLZGTAkmLhHV+0Uv6q6EDgDGC/OJDePxebjw9kzABJHfWUmB1bUMoHYYd93FEe9 BZQfwOKB4/l54vyRkojSsZ5zD5vqC0OWX232WL5oil6OqogXa+5/e93xZwRWCg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1674833780; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=Al4OaKu7vFhMp7A4vZrcnoaIwYRn4dQmCYM4rdMXs04=; b=va7D+cN2SBEiKAfOpdJ34O63zeGDajD+yrw0ljd7wwNjQbSHDbTMg9GxG6TwC9yak/4Vb2 ByyFH53BB3vPYHHUbTssTjvGfW+7HFb5n3NyX3HTwhAZzV4JtFgJwf7eaLVbpo5AsjXl84 FpGw5GLhYN0MhlVMg9j/6tX8uV/E8NusXDAzvE+f29vLHu7xfXCgm8pNb49+23ASO5cBcW 6HNTsTh0mndH7mBnmkGCPssKe3C9YexkB8caM1MUTKGoitA6x6VBA3DFJLRPu518mka9M7 RB+LJ4Fp3hnoLgbsaPrdAAA+gRr+Gad0n3WRAHnNQnw51MWx5gKQIrEr4nGYTQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1674833780; a=rsa-sha256; cv=none; b=UKae3qEOAFv74BdNpUkb5jtroh/eMRMnAXlm+p9/1N2PXcAUNf7XcZ7AOSOUjDDaUamM8B WfwSD+aVutkEW5G0yijUlv57G6i/Cc9eESPRsOyjCcbB8OgLc80idW2sAoNzhzFOeg/jTR 6zp82AUGU12k/JDP13LvJRFU0OaJvay6/4OVgfUN+6GjboSFXQJklCKmGiJVRDCx7ooNHG ceKgvIULQVDal0Cc/a2afAkYH9vTjcTScOmYaeQql2tfXuG4DAf5bbYTuUwPvXD7Aulliy G2vkcAnG1ja0wmTtHkXBze9A9OKaxBiHbaC1rfn08mYYz2YTCnHhGnn1RfNT8g== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4P3M9v63KszDrZ; Fri, 27 Jan 2023 15:36:19 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 30RFaJA1000147; Fri, 27 Jan 2023 15:36:19 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 30RFaJTH000146; Fri, 27 Jan 2023 15:36:19 GMT (envelope-from git) Date: Fri, 27 Jan 2023 15:36:19 GMT Message-Id: <202301271536.30RFaJTH000146@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: "Alexander V. Chernikov" Subject: git: 68636dcb6fde - main - netstat: make netstat -rn use netlink instead of rtsock List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: melifaro X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 68636dcb6fded1058318052454eabe35d1f65cb2 Auto-Submitted: auto-generated X-ThisMailContainsUnwantedMimeParts: N The branch main has been updated by melifaro: URL: https://cgit.FreeBSD.org/src/commit/?id=68636dcb6fded1058318052454eabe35d1f65cb2 commit 68636dcb6fded1058318052454eabe35d1f65cb2 Author: Alexander V. Chernikov AuthorDate: 2023-01-27 15:14:39 +0000 Commit: Alexander V. Chernikov CommitDate: 2023-01-27 15:36:10 +0000 netstat: make netstat -rn use netlink instead of rtsock This change switches route listing in netstat to netlink, with fallback to rtsock. The outputs are mostly identical, with an exception of not showing kernel nexthop indexes for multipath routes. Differential Revision: https://reviews.freebsd.org/D36529 --- usr.bin/netstat/Makefile | 1 + usr.bin/netstat/common.h | 21 +- usr.bin/netstat/route.c | 79 ++++--- usr.bin/netstat/route_netlink.c | 491 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 550 insertions(+), 42 deletions(-) diff --git a/usr.bin/netstat/Makefile b/usr.bin/netstat/Makefile index febab940be4a..e2c9ca09eea5 100644 --- a/usr.bin/netstat/Makefile +++ b/usr.bin/netstat/Makefile @@ -6,6 +6,7 @@ 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 \ + route_netlink.c \ nl_defs.h nl_symbols.c: nlist_symbols diff --git a/usr.bin/netstat/common.h b/usr.bin/netstat/common.h index da058c97d910..d5aadd50b34e 100644 --- a/usr.bin/netstat/common.h +++ b/usr.bin/netstat/common.h @@ -45,11 +45,28 @@ 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 print_sockaddr(const char *name, struct sockaddr *sa, - struct sockaddr *mask, int flags, int width); +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); diff --git a/usr.bin/netstat/route.c b/usr.bin/netstat/route.c index c2a68cae87e8..0026ee924854 100644 --- a/usr.bin/netstat/route.c +++ b/usr.bin/netstat/route.c @@ -106,7 +106,6 @@ 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 p_flags(int, const char *); static void domask(char *, size_t, u_long); @@ -143,7 +142,8 @@ routepr(int fibnum, int af) if (fibnum) xo_emit(" ({L:fib}: {:fib/%d})", fibnum); xo_emit("\n"); - p_rtable_sysctl(fibnum, af); + if (!p_rtable_netlink(fibnum, af)) + p_rtable_sysctl(fibnum, af); xo_close_container("route-information"); } @@ -197,42 +197,48 @@ pr_family(int af1) #define WID_IF_DEFAULT(af) ((af) == AF_INET6 ? 8 : (Wflag ? 10 : 8)) #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_expire; +struct _wid wid; /* * Print header for routing table columns. */ -static void +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_if, wid_if, "Netif", - wid_expire, "Expire"); + 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_if, wid_if, "Netif", - wid_expire, "Expire"); + 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) { @@ -278,15 +284,8 @@ p_rtable_sysctl(int fibnum, int af) xo_close_instance("rt-family"); } need_table_close = true; - fam = sa->sa_family; - wid_dst = WID_DST_DEFAULT(fam); - wid_gw = WID_GW_DEFAULT(fam); - wid_flags = 6; - wid_pksent = 8; - wid_mtu = 6; - wid_if = WID_IF_DEFAULT(fam); - wid_expire = 6; + set_wid(fam); xo_open_instance("rt-family"); pr_family(fam); xo_open_list("rt-entry"); @@ -323,22 +322,22 @@ p_rtentry_sysctl(const char *name, struct rt_msghdr *rtm) protrusion = p_sockaddr("destination", addr[RTAX_DST], addr[RTAX_NETMASK], - rtm->rtm_flags, wid_dst); + rtm->rtm_flags, wid.dst); protrusion = p_sockaddr("gateway", addr[RTAX_GATEWAY], NULL, RTF_HOST, - wid_gw - protrusion); + wid.gw - protrusion); snprintf(buffer, sizeof(buffer), "{[:-%d}{:flags/%%s}{]:} ", - wid_flags - protrusion); + 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); + 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); + xo_emit("{t:mtu/%*lu} ", wid.mtu, rtm->rtm_rmx.rmx_mtu); else - xo_emit("{P:/%*s} ", wid_mtu, ""); + xo_emit("{P:/%*s} ", wid.mtu, ""); } memset(prettyname, 0, sizeof(prettyname)); @@ -350,15 +349,15 @@ p_rtentry_sysctl(const char *name, struct rt_msghdr *rtm) } if (Wflag) - xo_emit("{t:interface-name/%*s}", wid_if, prettyname); + xo_emit("{t:interface-name/%*s}", wid.iface, prettyname); else - xo_emit("{t:interface-name/%*.*s}", wid_if, wid_if, + 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, + xo_emit(" {:expire-time/%*d}", wid.expire, (int)expire_time); } @@ -472,7 +471,7 @@ fmt_sockaddr(struct sockaddr *sa, struct sockaddr *mask, int flags) return (cp); } -static void +void p_flags(int f, const char *format) { diff --git a/usr.bin/netstat/route_netlink.c b/usr.bin/netstat/route_netlink.c new file mode 100644 index 000000000000..7c0ac8fea30c --- /dev/null +++ b/usr.bin/netstat/route_netlink.c @@ -0,0 +1,491 @@ +/*- + * 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(%lu) 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_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_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 }, +}; + +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) +{ + 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); + + 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); + + 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.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); + 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); + 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); +} + +