]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/netstat/route_netlink.c
ssh: Update to OpenSSH 9.3p1
[FreeBSD/FreeBSD.git] / usr.bin / netstat / route_netlink.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1983, 1988, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34
35 #include <sys/param.h>
36 #include <sys/protosw.h>
37 #include <sys/socket.h>
38 #include <sys/socketvar.h>
39 #include <sys/sysctl.h>
40 #include <sys/time.h>
41
42 #include <net/ethernet.h>
43 #include <net/if.h>
44 #include <net/if_dl.h>
45 #include <net/if_types.h>
46 #include <netlink/netlink.h>
47 #include <netlink/netlink_route.h>
48 #include <netlink/netlink_snl.h>
49 #include <netlink/netlink_snl_route.h>
50
51 #include <netinet/in.h>
52 #include <netgraph/ng_socket.h>
53
54 #include <arpa/inet.h>
55 #include <ifaddrs.h>
56 #include <libutil.h>
57 #include <netdb.h>
58 #include <stdbool.h>
59 #include <stdint.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <stdbool.h>
63 #include <string.h>
64 #include <sysexits.h>
65 #include <unistd.h>
66 #include <err.h>
67 #include <libxo/xo.h>
68 #include "netstat.h"
69 #include "common.h"
70 #include "nl_defs.h"
71
72
73 static void p_rtentry_netlink(struct snl_state *ss, const char *name, struct nlmsghdr *hdr);
74
75 static struct ifmap_entry *ifmap;
76 static size_t ifmap_size;
77
78 struct nl_parsed_link {
79         uint32_t                ifi_index;
80         uint32_t                ifla_mtu;
81         char                    *ifla_ifname;
82 };
83
84 #define _IN(_field)     offsetof(struct ifinfomsg, _field)
85 #define _OUT(_field)    offsetof(struct nl_parsed_link, _field)
86 static struct snl_attr_parser ap_link[] = {
87         { .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = snl_attr_get_string },
88         { .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = snl_attr_get_uint32 },
89 };
90 static struct snl_field_parser fp_link[] = {
91         {.off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = snl_field_get_uint32 },
92 };
93 #undef _IN
94 #undef _OUT
95 SNL_DECLARE_PARSER(link_parser, struct ifinfomsg, fp_link, ap_link);
96
97 /* Generate ifmap using netlink */
98 static struct ifmap_entry *
99 prepare_ifmap_netlink(struct snl_state *ss, size_t *pifmap_size)
100 {
101         struct {
102                 struct nlmsghdr hdr;
103                 struct ifinfomsg ifmsg;
104         } msg = {
105                 .hdr.nlmsg_type = RTM_GETLINK,
106                 .hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
107                 .hdr.nlmsg_seq = snl_get_seq(ss),
108         };
109         msg.hdr.nlmsg_len = sizeof(msg);
110
111         if (!snl_send(ss, &msg, sizeof(msg))) {
112                 snl_free(ss);
113                 return (NULL);
114         }
115
116         struct ifmap_entry *ifmap = NULL;
117         uint32_t ifmap_size = 0;
118         struct nlmsghdr *hdr;
119         while ((hdr = snl_read_message(ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
120                 if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
121                         continue;
122 /*
123                 if (hdr->nlmsg_type == NLMSG_ERROR)
124                         break;
125 */
126                 struct nl_parsed_link link = {};
127                 if (!snl_parse_nlmsg(ss, hdr, &link_parser, &link))
128                         continue;
129                 if (link.ifi_index >= ifmap_size) {
130                         size_t size = roundup2(link.ifi_index + 1, 32) * sizeof(struct ifmap_entry);
131                         if ((ifmap = realloc(ifmap, size)) == NULL)
132                                 errx(2, "realloc(%zu) failed", size);
133                         memset(&ifmap[ifmap_size], 0,
134                             size - ifmap_size *
135                             sizeof(struct ifmap_entry));
136                         ifmap_size = roundup2(link.ifi_index + 1, 32);
137                 }
138                 if (*ifmap[link.ifi_index].ifname != '\0')
139                         continue;
140                 strlcpy(ifmap[link.ifi_index].ifname, link.ifla_ifname, IFNAMSIZ);
141                 ifmap[link.ifi_index].mtu = link.ifla_mtu;
142         }
143         *pifmap_size = ifmap_size;
144         return (ifmap);
145 }
146
147 struct rta_mpath_nh {
148         struct sockaddr *gw;
149         uint32_t        ifindex;
150         uint8_t         rtnh_flags;
151         uint8_t         rtnh_weight;
152         uint32_t        rtax_mtu;
153         uint32_t        rta_rtflags;
154 };
155
156 #define _IN(_field)     offsetof(struct rtnexthop, _field)
157 #define _OUT(_field)    offsetof(struct rta_mpath_nh, _field)
158 static const struct snl_attr_parser nla_p_mp_rtmetrics[] = {
159         { .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = snl_attr_get_uint32 },
160 };
161 SNL_DECLARE_ATTR_PARSER(metrics_mp_parser, nla_p_mp_rtmetrics);
162
163 static const struct snl_attr_parser psnh[] = {
164         { .type = NL_RTA_GATEWAY, .off = _OUT(gw), .cb = snl_attr_get_ip },
165         { .type = NL_RTA_METRICS, .arg = &metrics_mp_parser, .cb = snl_attr_get_nested },
166         { .type = NL_RTA_RTFLAGS, .off = _OUT(rta_rtflags), .cb = snl_attr_get_uint32 },
167         { .type = NL_RTA_VIA, .off = _OUT(gw), .cb = snl_attr_get_ipvia },
168 };
169
170 static const struct snl_field_parser fpnh[] = {
171         { .off_in = _IN(rtnh_flags), .off_out = _OUT(rtnh_flags), .cb = snl_field_get_uint8 },
172         { .off_in = _IN(rtnh_hops), .off_out = _OUT(rtnh_weight), .cb = snl_field_get_uint8 },
173         { .off_in = _IN(rtnh_ifindex), .off_out = _OUT(ifindex), .cb = snl_field_get_uint32 },
174 };
175 #undef _IN
176 #undef _OUT
177
178 SNL_DECLARE_PARSER(mpath_parser, struct rtnexthop, fpnh, psnh);
179
180 struct rta_mpath {
181         int num_nhops;
182         struct rta_mpath_nh nhops[0];
183 };
184
185 static bool
186 nlattr_get_multipath(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target)
187 {
188         int data_len = nla->nla_len - sizeof(struct nlattr);
189         struct rtnexthop *rtnh;
190
191         int max_nhops = data_len / sizeof(struct rtnexthop);
192         size_t sz = (max_nhops + 2) * sizeof(struct rta_mpath_nh);
193
194         struct rta_mpath *mp = snl_allocz(ss, sz);
195         mp->num_nhops = 0;
196
197         for (rtnh = (struct rtnexthop *)(nla + 1); data_len > 0; ) {
198                 struct rta_mpath_nh *mpnh = &mp->nhops[mp->num_nhops++];
199
200                 if (!snl_parse_header(ss, rtnh, rtnh->rtnh_len, &mpath_parser, mpnh))
201                         return (false);
202
203                 int len = NL_ITEM_ALIGN(rtnh->rtnh_len);
204                 data_len -= len;
205                 rtnh = (struct rtnexthop *)((char *)rtnh + len);
206         }
207         if (data_len != 0 || mp->num_nhops == 0) {
208                 return (false);
209         }
210
211         *((struct rta_mpath **)target) = mp;
212         return (true);
213 }
214
215
216 struct nl_parsed_route {
217         struct sockaddr         *rta_dst;
218         struct sockaddr         *rta_gw;
219         struct nlattr           *rta_metrics;
220         struct rta_mpath        *rta_multipath;
221         uint32_t                rta_expires;
222         uint32_t                rta_oif;
223         uint32_t                rta_expire;
224         uint32_t                rta_table;
225         uint32_t                rta_knh_id;
226         uint32_t                rta_nh_id;
227         uint32_t                rta_rtflags;
228         uint32_t                rtax_mtu;
229         uint32_t                rtax_weight;
230         uint8_t                 rtm_family;
231         uint8_t                 rtm_type;
232         uint8_t                 rtm_protocol;
233         uint8_t                 rtm_dst_len;
234 };
235
236 #define _IN(_field)     offsetof(struct rtmsg, _field)
237 #define _OUT(_field)    offsetof(struct nl_parsed_route, _field)
238 static const struct snl_attr_parser nla_p_rtmetrics[] = {
239         { .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = snl_attr_get_uint32 },
240 };
241 SNL_DECLARE_ATTR_PARSER(metrics_parser, nla_p_rtmetrics);
242
243 static const struct snl_attr_parser ps[] = {
244         { .type = NL_RTA_DST, .off = _OUT(rta_dst), .cb = snl_attr_get_ip },
245         { .type = NL_RTA_OIF, .off = _OUT(rta_oif), .cb = snl_attr_get_uint32 },
246         { .type = NL_RTA_GATEWAY, .off = _OUT(rta_gw), .cb = snl_attr_get_ip },
247         { .type = NL_RTA_METRICS, .arg = &metrics_parser, .cb = snl_attr_get_nested },
248         { .type = NL_RTA_MULTIPATH, .off = _OUT(rta_multipath), .cb = nlattr_get_multipath },
249         { .type = NL_RTA_KNH_ID, .off = _OUT(rta_knh_id), .cb = snl_attr_get_uint32 },
250         { .type = NL_RTA_WEIGHT, .off = _OUT(rtax_weight), .cb = snl_attr_get_uint32 },
251         { .type = NL_RTA_RTFLAGS, .off = _OUT(rta_rtflags), .cb = snl_attr_get_uint32 },
252         { .type = NL_RTA_TABLE, .off = _OUT(rta_table), .cb = snl_attr_get_uint32 },
253         { .type = NL_RTA_VIA, .off = _OUT(rta_gw), .cb = snl_attr_get_ipvia },
254         { .type = NL_RTA_EXPIRES, .off = _OUT(rta_expire), .cb = snl_attr_get_uint32 },
255         { .type = NL_RTA_NH_ID, .off = _OUT(rta_nh_id), .cb = snl_attr_get_uint32 },
256 };
257
258 static const struct snl_field_parser fprt[] = {
259         {.off_in = _IN(rtm_family), .off_out = _OUT(rtm_family), .cb = snl_field_get_uint8 },
260         {.off_in = _IN(rtm_type), .off_out = _OUT(rtm_type), .cb = snl_field_get_uint8 },
261         {.off_in = _IN(rtm_protocol), .off_out = _OUT(rtm_protocol), .cb = snl_field_get_uint8 },
262         {.off_in = _IN(rtm_dst_len), .off_out = _OUT(rtm_dst_len), .cb = snl_field_get_uint8 },
263 };
264 #undef _IN
265 #undef _OUT
266 SNL_DECLARE_PARSER(rtm_parser, struct rtmsg, fprt, ps);
267
268 #define RTF_UP          0x1
269 #define RTF_GATEWAY     0x2
270 #define RTF_HOST        0x4
271 #define RTF_REJECT      0x8
272 #define RTF_DYNAMIC     0x10
273 #define RTF_STATIC      0x800
274 #define RTF_BLACKHOLE   0x1000
275 #define RTF_PROTO2      0x4000
276 #define RTF_PROTO1      0x8000
277 #define RTF_PROTO3      0x40000
278 #define RTF_FIXEDMTU    0x80000
279 #define RTF_PINNED      0x100000
280
281 static void
282 ip6_writemask(struct in6_addr *addr6, uint8_t mask)
283 {
284         uint32_t *cp;
285
286         for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32)
287                 *cp++ = 0xFFFFFFFF;
288         if (mask > 0)
289                 *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
290 }
291
292 static void
293 gen_mask(int family, int plen, struct sockaddr *sa)
294 {
295         if (family == AF_INET6) {
296                 struct sockaddr_in6 sin6 = {
297                         .sin6_family = AF_INET6,
298                         .sin6_len = sizeof(struct sockaddr_in6),
299                 };
300                 ip6_writemask(&sin6.sin6_addr, plen);
301                 *((struct sockaddr_in6 *)sa) = sin6;
302         } else if (family == AF_INET) {
303                 struct sockaddr_in sin = {
304                         .sin_family = AF_INET,
305                         .sin_len = sizeof(struct sockaddr_in),
306                         .sin_addr.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0),
307                 };
308                 *((struct sockaddr_in *)sa) = sin;
309         }
310 }
311
312 struct sockaddr_dl_short {
313         u_char  sdl_len;        /* Total length of sockaddr */
314         u_char  sdl_family;     /* AF_LINK */
315         u_short sdl_index;      /* if != 0, system given index for interface */
316         u_char  sdl_type;       /* interface type */
317         u_char  sdl_nlen;       /* interface name length, no trailing 0 reqd. */
318         u_char  sdl_alen;       /* link level address length */
319         u_char  sdl_slen;       /* link layer selector length */
320         char    sdl_data[8];    /* unused */
321 };
322
323 static void
324 add_scopeid(struct sockaddr *sa, int ifindex)
325 {
326         if (sa->sa_family == AF_INET6) {
327                 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
328                 if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
329                         sin6->sin6_scope_id = ifindex;
330         }
331 }
332
333 static void
334 p_path(struct nl_parsed_route *rt, bool is_mpath)
335 {
336         struct sockaddr_in6 mask6;
337         struct sockaddr *pmask = (struct sockaddr *)&mask6;
338         char buffer[128];
339         char prettyname[128];
340         int protrusion;
341
342         gen_mask(rt->rtm_family, rt->rtm_dst_len, pmask);
343         add_scopeid(rt->rta_dst, rt->rta_oif);
344         add_scopeid(rt->rta_gw, rt->rta_oif);
345         protrusion = p_sockaddr("destination", rt->rta_dst, pmask, rt->rta_rtflags, wid.dst);
346         protrusion = p_sockaddr("gateway", rt->rta_gw, NULL, RTF_HOST,
347             wid.gw - protrusion);
348         snprintf(buffer, sizeof(buffer), "{[:-%d}{:flags/%%s}{]:} ",
349             wid.flags - protrusion);
350         p_flags(rt->rta_rtflags | RTF_UP, buffer);
351         /* Output path weight as non-visual property */
352         xo_emit("{e:weight/%u}", rt->rtax_weight);
353         if (is_mpath)
354                 xo_emit("{e:nhg-kidx/%u}", rt->rta_knh_id);
355         else
356                 xo_emit("{e:nhop-kidx/%u}", rt->rta_knh_id);
357         if (rt->rta_nh_id != 0) {
358                 if (is_mpath)
359                         xo_emit("{e:nhg-uidx/%u}", rt->rta_nh_id);
360                 else
361                         xo_emit("{e:nhop-uidx/%u}", rt->rta_nh_id);
362         }
363
364         memset(prettyname, 0, sizeof(prettyname));
365         if (rt->rta_oif < ifmap_size) {
366                 strlcpy(prettyname, ifmap[rt->rta_oif].ifname,
367                     sizeof(prettyname));
368                 if (*prettyname == '\0')
369                         strlcpy(prettyname, "---", sizeof(prettyname));
370                 if (rt->rtax_mtu == 0)
371                         rt->rtax_mtu = ifmap[rt->rta_oif].mtu;
372         }
373
374         if (Wflag) {
375                 /* XXX: use=0? */
376                 xo_emit("{t:nhop/%*lu} ", wid.mtu, is_mpath ? 0 : rt->rta_knh_id);
377
378                 if (rt->rtax_mtu != 0)
379                         xo_emit("{t:mtu/%*lu} ", wid.mtu, rt->rtax_mtu);
380                 else {
381                         /* use interface mtu */
382                         xo_emit("{P:/%*s} ", wid.mtu, "");
383                 }
384
385         }
386
387         if (Wflag)
388                 xo_emit("{t:interface-name/%*s}", wid.iface, prettyname);
389         else
390                 xo_emit("{t:interface-name/%*.*s}", wid.iface, wid.iface,
391                     prettyname);
392         if (rt->rta_expires > 0) {
393                 xo_emit(" {:expire-time/%*u}", wid.expire, rt->rta_expires);
394         }
395 }
396
397 static void
398 p_rtentry_netlink(struct snl_state *ss, const char *name, struct nlmsghdr *hdr)
399 {
400
401         struct nl_parsed_route rt = {};
402         if (!snl_parse_nlmsg(ss, hdr, &rtm_parser, &rt))
403                 return;
404         if (rt.rtax_weight == 0)
405                 rt.rtax_weight = rt_default_weight;
406
407         if (rt.rta_multipath != NULL) {
408                 uint32_t orig_rtflags = rt.rta_rtflags;
409                 uint32_t orig_mtu = rt.rtax_mtu;
410                 for (int i = 0; i < rt.rta_multipath->num_nhops; i++) {
411                         struct rta_mpath_nh *nhop = &rt.rta_multipath->nhops[i];
412
413                         rt.rta_gw = nhop->gw;
414                         rt.rta_oif = nhop->ifindex;
415                         rt.rtax_weight = nhop->rtnh_weight;
416                         rt.rta_rtflags = nhop->rta_rtflags ? nhop->rta_rtflags : orig_rtflags;
417                         rt.rtax_mtu = nhop->rtax_mtu ? nhop->rtax_mtu : orig_mtu;
418
419                         xo_open_instance(name);
420                         p_path(&rt, true);
421                         xo_emit("\n");
422                         xo_close_instance(name);
423                 }
424                 return;
425         }
426
427         struct sockaddr_dl_short sdl_gw = {
428                 .sdl_family = AF_LINK,
429                 .sdl_len = sizeof(struct sockaddr_dl_short),
430                 .sdl_index = rt.rta_oif,
431         };
432         if (rt.rta_gw == NULL)
433                 rt.rta_gw = (struct sockaddr *)&sdl_gw;
434
435         xo_open_instance(name);
436         p_path(&rt, false);
437         xo_emit("\n");
438         xo_close_instance(name);
439 }
440
441 static const struct snl_hdr_parser *all_parsers[] = {
442         &link_parser, &metrics_mp_parser, &mpath_parser, &metrics_parser, &rtm_parser
443 };
444
445 bool
446 p_rtable_netlink(int fibnum, int af)
447 {
448         int fam = AF_UNSPEC;
449         int need_table_close = false;
450         struct nlmsghdr *hdr;
451
452         struct snl_state ss = {};
453
454         SNL_VERIFY_PARSERS(all_parsers);
455
456         if (!snl_init(&ss, NETLINK_ROUTE))
457                 return (false);
458
459         ifmap = prepare_ifmap_netlink(&ss, &ifmap_size);
460
461         struct {
462                 struct nlmsghdr hdr;
463                 struct rtmsg rtmsg;
464                 struct nlattr nla_fibnum;
465                 uint32_t fibnum;
466         } msg = {
467                 .hdr.nlmsg_type = RTM_GETROUTE,
468                 .hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
469                 .hdr.nlmsg_seq = snl_get_seq(&ss),
470                 .rtmsg.rtm_family = af,
471                 .nla_fibnum.nla_len = sizeof(struct nlattr) + sizeof(uint32_t),
472                 .nla_fibnum.nla_type = RTA_TABLE,
473                 .fibnum = fibnum,
474         };
475         msg.hdr.nlmsg_len = sizeof(msg);
476
477         if (!snl_send(&ss, &msg, sizeof(msg))) {
478                 snl_free(&ss);
479                 return (false);
480         }
481
482         xo_open_container("route-table");
483         xo_open_list("rt-family");
484         while ((hdr = snl_read_message(&ss)) != NULL && hdr->nlmsg_type != NLMSG_DONE) {
485                 if (hdr->nlmsg_seq != msg.hdr.nlmsg_seq)
486                         continue;
487                 struct rtmsg *rtm = (struct rtmsg *)(hdr + 1);
488                 /* Only print family first time. */
489                 if (fam != rtm->rtm_family) {
490                         if (need_table_close) {
491                                 xo_close_list("rt-entry");
492                                 xo_close_instance("rt-family");
493                         }
494                         need_table_close = true;
495                         fam = rtm->rtm_family;
496                         set_wid(fam);
497                         xo_open_instance("rt-family");
498                         pr_family(fam);
499                         xo_open_list("rt-entry");
500                         pr_rthdr(fam);
501                 }
502                 p_rtentry_netlink(&ss, "rt-entry", hdr);
503                 snl_clear_lb(&ss);
504         }
505         if (need_table_close) {
506                 xo_close_list("rt-entry");
507                 xo_close_instance("rt-family");
508         }
509         xo_close_list("rt-family");
510         xo_close_container("route-table");
511         snl_free(&ss);
512         return (true);
513 }
514
515