xref: /freebsd/sys/netlink/route/rt.c (revision a466cc55373fc3cf86837f09da729535b57e69a1)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 Ng Peng Nam Sean
5  * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
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  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "opt_netlink.h"
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 #include "opt_inet.h"
34 #include "opt_inet6.h"
35 #include "opt_route.h"
36 #include <sys/types.h>
37 #include <sys/malloc.h>
38 #include <sys/rmlock.h>
39 #include <sys/socket.h>
40 
41 #include <net/if.h>
42 #include <net/route.h>
43 #include <net/route/nhop.h>
44 #include <net/route/route_ctl.h>
45 #include <net/route/route_var.h>
46 #include <netinet6/scope6_var.h>
47 #include <netlink/netlink.h>
48 #include <netlink/netlink_ctl.h>
49 #include <netlink/netlink_route.h>
50 #include <netlink/route/route_var.h>
51 
52 #define	DEBUG_MOD_NAME	nl_route
53 #define	DEBUG_MAX_LEVEL	LOG_DEBUG3
54 #include <netlink/netlink_debug.h>
55 _DECLARE_DEBUG(LOG_INFO);
56 
57 static unsigned char
58 get_rtm_type(const struct nhop_object *nh)
59 {
60 	int nh_flags = nh->nh_flags;
61 
62 	/* Use the fact that nhg runtime flags are only NHF_MULTIPATH */
63 	if (nh_flags & NHF_BLACKHOLE)
64 		return (RTN_BLACKHOLE);
65 	else if (nh_flags & NHF_REJECT)
66 		return (RTN_PROHIBIT);
67 	return (RTN_UNICAST);
68 }
69 
70 static uint8_t
71 nl_get_rtm_protocol(const struct nhop_object *nh)
72 {
73 #ifdef ROUTE_MPATH
74 	if (NH_IS_NHGRP(nh)) {
75 		const struct nhgrp_object *nhg = (const struct nhgrp_object *)nh;
76 		uint8_t origin = nhgrp_get_origin(nhg);
77 		if (origin != RTPROT_UNSPEC)
78 			return (origin);
79 		nh = nhg->nhops[0];
80 	}
81 #endif
82 	uint8_t origin = nhop_get_origin(nh);
83 	if (origin != RTPROT_UNSPEC)
84 		return (origin);
85 	/* TODO: remove guesswork once all kernel users fill in origin */
86 	int rt_flags = nhop_get_rtflags(nh);
87 	if (rt_flags & RTF_PROTO1)
88 		return (RTPROT_ZEBRA);
89 	if (rt_flags & RTF_STATIC)
90 		return (RTPROT_STATIC);
91 	return (RTPROT_KERNEL);
92 }
93 
94 static int
95 get_rtmsg_type_from_rtsock(int cmd)
96 {
97 	switch (cmd) {
98 	case RTM_ADD:
99 	case RTM_CHANGE:
100 	case RTM_GET:
101 		return NL_RTM_NEWROUTE;
102 	case RTM_DELETE:
103 		return NL_RTM_DELROUTE;
104 	}
105 
106 	return (0);
107 }
108 
109 /*
110  * fibnum heuristics
111  *
112  * if (dump && rtm_table == 0 && !rta_table) RT_ALL_FIBS
113  * msg                rtm_table     RTA_TABLE            result
114  * RTM_GETROUTE/dump          0             -       RT_ALL_FIBS
115  * RTM_GETROUTE/dump          1             -                 1
116  * RTM_GETROUTE/get           0             -                 0
117  *
118  */
119 
120 static struct nhop_object *
121 rc_get_nhop(const struct rib_cmd_info *rc)
122 {
123 	return ((rc->rc_cmd == RTM_DELETE) ? rc->rc_nh_old : rc->rc_nh_new);
124 }
125 
126 static void
127 dump_rc_nhop_gw(struct nl_writer *nw, const struct nhop_object *nh)
128 {
129 #ifdef INET6
130 	int upper_family;
131 #endif
132 
133 	switch (nhop_get_neigh_family(nh)) {
134 	case AF_LINK:
135 		/* onlink prefix, skip */
136 		break;
137 	case AF_INET:
138 		nlattr_add(nw, NL_RTA_GATEWAY, 4, &nh->gw4_sa.sin_addr);
139 		break;
140 #ifdef INET6
141 	case AF_INET6:
142 		upper_family = nhop_get_upper_family(nh);
143 		if (upper_family == AF_INET6) {
144 			struct in6_addr gw6 = nh->gw6_sa.sin6_addr;
145 			in6_clearscope(&gw6);
146 
147 			nlattr_add(nw, NL_RTA_GATEWAY, 16, &gw6);
148 		} else if (upper_family == AF_INET) {
149 			/* IPv4 over IPv6 */
150 			struct in6_addr gw6 = nh->gw6_sa.sin6_addr;
151 			in6_clearscope(&gw6);
152 
153 			char buf[20];
154 			struct rtvia *via = (struct rtvia *)&buf[0];
155 			via->rtvia_family = AF_INET6;
156 			memcpy(via->rtvia_addr, &gw6, 16);
157 			nlattr_add(nw, NL_RTA_VIA, 17, via);
158 		}
159 		break;
160 #endif
161 	}
162 }
163 
164 static void
165 dump_rc_nhop_mtu(struct nl_writer *nw, const struct nhop_object *nh)
166 {
167 	int nla_len = sizeof(struct nlattr) * 2 + sizeof(uint32_t);
168 	struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr);
169 
170 	if (nla == NULL)
171 		return;
172 	nla->nla_type = NL_RTA_METRICS;
173 	nla->nla_len = nla_len;
174 	nla++;
175 	nla->nla_type = NL_RTAX_MTU;
176 	nla->nla_len = sizeof(struct nlattr) + sizeof(uint32_t);
177 	*((uint32_t *)(nla + 1)) = nh->nh_mtu;
178 }
179 
180 #ifdef ROUTE_MPATH
181 static void
182 dump_rc_nhg(struct nl_writer *nw, const struct nhgrp_object *nhg, struct rtmsg *rtm)
183 {
184 	uint32_t uidx = nhgrp_get_uidx(nhg);
185 	uint32_t num_nhops;
186 	const struct weightened_nhop *wn = nhgrp_get_nhops(nhg, &num_nhops);
187 	uint32_t base_rtflags = nhop_get_rtflags(wn[0].nh);
188 
189 	if (uidx != 0)
190 		nlattr_add_u32(nw, NL_RTA_NH_ID, uidx);
191 	nlattr_add_u32(nw, NL_RTA_KNH_ID, nhgrp_get_idx(nhg));
192 
193 	nlattr_add_u32(nw, NL_RTA_RTFLAGS, base_rtflags);
194 	int off = nlattr_add_nested(nw, NL_RTA_MULTIPATH);
195 	if (off == 0)
196 		return;
197 
198 	for (int i = 0; i < num_nhops; i++) {
199 		int nh_off = nlattr_save_offset(nw);
200 		struct rtnexthop *rtnh = nlmsg_reserve_object(nw, struct rtnexthop);
201 		if (rtnh == NULL)
202 			return;
203 		rtnh->rtnh_flags = 0;
204 		rtnh->rtnh_ifindex = wn[i].nh->nh_ifp->if_index;
205 		rtnh->rtnh_hops = wn[i].weight;
206 		dump_rc_nhop_gw(nw, wn[i].nh);
207 		uint32_t rtflags = nhop_get_rtflags(wn[i].nh);
208 		if (rtflags != base_rtflags)
209 			nlattr_add_u32(nw, NL_RTA_RTFLAGS, rtflags);
210 		if (rtflags & RTF_FIXEDMTU)
211 			dump_rc_nhop_mtu(nw, wn[i].nh);
212 		rtnh = nlattr_restore_offset(nw, nh_off, struct rtnexthop);
213 		/*
214 		 * nlattr_add() allocates 4-byte aligned storage, no need to aligh
215 		 * length here
216 		 * */
217 		rtnh->rtnh_len = nlattr_save_offset(nw) - nh_off;
218 	}
219 	nlattr_set_len(nw, off);
220 }
221 #endif
222 
223 static void
224 dump_rc_nhop(struct nl_writer *nw, const struct route_nhop_data *rnd, struct rtmsg *rtm)
225 {
226 #ifdef ROUTE_MPATH
227 	if (NH_IS_NHGRP(rnd->rnd_nhop)) {
228 		dump_rc_nhg(nw, rnd->rnd_nhgrp, rtm);
229 		return;
230 	}
231 #endif
232 	const struct nhop_object *nh = rnd->rnd_nhop;
233 	uint32_t rtflags = nhop_get_rtflags(nh);
234 
235 	/*
236 	 * IPv4 over IPv6
237 	 *    ('RTA_VIA', {'family': 10, 'addr': 'fe80::20c:29ff:fe67:2dd'}), ('RTA_OIF', 2),
238 	 * IPv4 w/ gw
239 	 *    ('RTA_GATEWAY', '172.16.107.131'), ('RTA_OIF', 2)],
240 	 * Direct route:
241 	 *    ('RTA_OIF', 2)
242 	 */
243 	if (nh->nh_flags & NHF_GATEWAY)
244 		dump_rc_nhop_gw(nw, nh);
245 
246 	uint32_t uidx = nhop_get_uidx(nh);
247 	if (uidx != 0)
248 		nlattr_add_u32(nw, NL_RTA_NH_ID, uidx);
249 	nlattr_add_u32(nw, NL_RTA_KNH_ID, nhop_get_idx(nh));
250 	nlattr_add_u32(nw, NL_RTA_RTFLAGS, rtflags);
251 
252 	if (rtflags & RTF_FIXEDMTU)
253 		dump_rc_nhop_mtu(nw, nh);
254 	uint32_t nh_expire = nhop_get_expire(nh);
255 	if (nh_expire > 0)
256 		nlattr_add_u32(nw, NL_RTA_EXPIRES, nh_expire - time_uptime);
257 
258 	/* In any case, fill outgoing interface */
259 	nlattr_add_u32(nw, NL_RTA_OIF, nh->nh_ifp->if_index);
260 
261 	if (rnd->rnd_weight != RT_DEFAULT_WEIGHT)
262 		nlattr_add_u32(nw, NL_RTA_WEIGHT, rnd->rnd_weight);
263 }
264 
265 /*
266  * Dumps output from a rib command into an rtmsg
267  */
268 
269 static int
270 dump_px(uint32_t fibnum, const struct nlmsghdr *hdr,
271     const struct rtentry *rt, struct route_nhop_data *rnd,
272     struct nl_writer *nw)
273 {
274 	struct rtmsg *rtm;
275 	int error = 0;
276 
277 	NET_EPOCH_ASSERT();
278 
279 	if (!nlmsg_reply(nw, hdr, sizeof(struct rtmsg)))
280 		goto enomem;
281 
282 	int family = rt_get_family(rt);
283 	int rtm_off = nlattr_save_offset(nw);
284 	rtm = nlmsg_reserve_object(nw, struct rtmsg);
285 	rtm->rtm_family = family;
286 	rtm->rtm_dst_len = 0;
287 	rtm->rtm_src_len = 0;
288 	rtm->rtm_tos = 0;
289 	if (fibnum < 255)
290 		rtm->rtm_table = (unsigned char)fibnum;
291 	rtm->rtm_scope = RT_SCOPE_UNIVERSE;
292 	rtm->rtm_protocol = nl_get_rtm_protocol(rnd->rnd_nhop);
293 	rtm->rtm_type = get_rtm_type(rnd->rnd_nhop);
294 
295 	nlattr_add_u32(nw, NL_RTA_TABLE, fibnum);
296 
297 	int plen = 0;
298 #if defined(INET) || defined(INET6)
299 	uint32_t scopeid;
300 #endif
301 	switch (family) {
302 #ifdef INET
303 	case AF_INET:
304 		{
305 			struct in_addr addr;
306 			rt_get_inet_prefix_plen(rt, &addr, &plen, &scopeid);
307 			nlattr_add(nw, NL_RTA_DST, 4, &addr);
308 			break;
309 		}
310 #endif
311 #ifdef INET6
312 	case AF_INET6:
313 		{
314 			struct in6_addr addr;
315 			rt_get_inet6_prefix_plen(rt, &addr, &plen, &scopeid);
316 			nlattr_add(nw, NL_RTA_DST, 16, &addr);
317 			break;
318 		}
319 #endif
320 	default:
321 		FIB_LOG(LOG_NOTICE, fibnum, family, "unsupported rt family: %d", family);
322 		error = EAFNOSUPPORT;
323 		goto flush;
324 	}
325 
326 	rtm = nlattr_restore_offset(nw, rtm_off, struct rtmsg);
327 	if (plen > 0)
328 		rtm->rtm_dst_len = plen;
329 	dump_rc_nhop(nw, rnd, rtm);
330 
331 	if (nlmsg_end(nw))
332 		return (0);
333 enomem:
334 	error = ENOMEM;
335 flush:
336 	nlmsg_abort(nw);
337 	return (error);
338 }
339 
340 static int
341 family_to_group(int family)
342 {
343 	switch (family) {
344 	case AF_INET:
345 		return (RTNLGRP_IPV4_ROUTE);
346 	case AF_INET6:
347 		return (RTNLGRP_IPV6_ROUTE);
348 	}
349 	return (0);
350 }
351 
352 static void
353 report_operation(uint32_t fibnum, struct rib_cmd_info *rc,
354     struct nlpcb *nlp, struct nlmsghdr *hdr)
355 {
356 	struct nl_writer nw = {};
357 	uint32_t group_id = family_to_group(rt_get_family(rc->rc_rt));
358 
359 	if (nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, group_id)) {
360 		struct route_nhop_data rnd = {
361 			.rnd_nhop = rc_get_nhop(rc),
362 			.rnd_weight = rc->rc_nh_weight,
363 		};
364 		hdr->nlmsg_flags &= ~(NLM_F_REPLACE | NLM_F_CREATE);
365 		hdr->nlmsg_flags &= ~(NLM_F_EXCL | NLM_F_APPEND);
366 		switch (rc->rc_cmd) {
367 		case RTM_ADD:
368 			hdr->nlmsg_type = NL_RTM_NEWROUTE;
369 			hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
370 			break;
371 		case RTM_CHANGE:
372 			hdr->nlmsg_type = NL_RTM_NEWROUTE;
373 			hdr->nlmsg_flags |= NLM_F_REPLACE;
374 			break;
375 		case RTM_DELETE:
376 			hdr->nlmsg_type = NL_RTM_DELROUTE;
377 			break;
378 		}
379 		dump_px(fibnum, hdr, rc->rc_rt, &rnd, &nw);
380 		nlmsg_flush(&nw);
381 	}
382 
383 	rtsock_callback_p->route_f(fibnum, rc);
384 }
385 
386 static void
387 set_scope6(struct sockaddr *sa, struct ifnet *ifp)
388 {
389 #ifdef INET6
390 	if (sa != NULL && sa->sa_family == AF_INET6 && ifp != NULL) {
391 		struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa;
392 
393 		if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr))
394 			in6_set_unicast_scopeid(&sa6->sin6_addr, if_getindex(ifp));
395 	}
396 #endif
397 }
398 
399 struct rta_mpath_nh {
400 	struct sockaddr	*gw;
401 	struct ifnet	*ifp;
402 	uint8_t		rtnh_flags;
403 	uint8_t		rtnh_weight;
404 };
405 
406 #define	_IN(_field)	offsetof(struct rtnexthop, _field)
407 #define	_OUT(_field)	offsetof(struct rta_mpath_nh, _field)
408 const static struct nlattr_parser nla_p_rtnh[] = {
409 	{ .type = NL_RTA_GATEWAY, .off = _OUT(gw), .cb = nlattr_get_ip },
410 	{ .type = NL_RTA_VIA, .off = _OUT(gw), .cb = nlattr_get_ipvia },
411 };
412 const static struct nlfield_parser nlf_p_rtnh[] = {
413 	{ .off_in = _IN(rtnh_flags), .off_out = _OUT(rtnh_flags), .cb = nlf_get_u8 },
414 	{ .off_in = _IN(rtnh_hops), .off_out = _OUT(rtnh_weight), .cb = nlf_get_u8 },
415 	{ .off_in = _IN(rtnh_ifindex), .off_out = _OUT(ifp), .cb = nlf_get_ifpz },
416 };
417 #undef _IN
418 #undef _OUT
419 
420 static bool
421 post_p_rtnh(void *_attrs, struct nl_pstate *npt __unused)
422 {
423 	struct rta_mpath_nh *attrs = (struct rta_mpath_nh *)_attrs;
424 
425 	set_scope6(attrs->gw, attrs->ifp);
426 	return (true);
427 }
428 NL_DECLARE_PARSER_EXT(mpath_parser, struct rtnexthop, NULL, nlf_p_rtnh, nla_p_rtnh, post_p_rtnh);
429 
430 struct rta_mpath {
431 	int num_nhops;
432 	struct rta_mpath_nh nhops[0];
433 };
434 
435 static int
436 nlattr_get_multipath(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
437 {
438 	int data_len = nla->nla_len - sizeof(struct nlattr);
439 	struct rtnexthop *rtnh;
440 
441 	int max_nhops = data_len / sizeof(struct rtnexthop);
442 
443 	struct rta_mpath *mp = npt_alloc(npt, (max_nhops + 2) * sizeof(struct rta_mpath_nh));
444 	mp->num_nhops = 0;
445 
446 	for (rtnh = (struct rtnexthop *)(nla + 1); data_len > 0; ) {
447 		struct rta_mpath_nh *mpnh = &mp->nhops[mp->num_nhops++];
448 
449 		int error = nl_parse_header(rtnh, rtnh->rtnh_len, &mpath_parser,
450 		    npt, mpnh);
451 		if (error != 0) {
452 			NLMSG_REPORT_ERR_MSG(npt, "RTA_MULTIPATH: nexhop %d: parse failed",
453 			    mp->num_nhops - 1);
454 			return (error);
455 		}
456 
457 		int len = NL_ITEM_ALIGN(rtnh->rtnh_len);
458 		data_len -= len;
459 		rtnh = (struct rtnexthop *)((char *)rtnh + len);
460 	}
461 	if (data_len != 0 || mp->num_nhops == 0) {
462 		NLMSG_REPORT_ERR_MSG(npt, "invalid RTA_MULTIPATH attr");
463 		return (EINVAL);
464 	}
465 
466 	*((struct rta_mpath **)target) = mp;
467 	return (0);
468 }
469 
470 
471 struct nl_parsed_route {
472 	struct sockaddr		*rta_dst;
473 	struct sockaddr		*rta_gw;
474 	struct ifnet		*rta_oif;
475 	struct rta_mpath	*rta_multipath;
476 	uint32_t		rta_table;
477 	uint32_t		rta_rtflags;
478 	uint32_t		rta_nh_id;
479 	uint32_t		rta_weight;
480 	uint32_t		rtax_mtu;
481 	uint8_t			rtm_family;
482 	uint8_t			rtm_dst_len;
483 	uint8_t			rtm_protocol;
484 	uint8_t			rtm_type;
485 	uint32_t		rtm_flags;
486 };
487 
488 #define	_IN(_field)	offsetof(struct rtmsg, _field)
489 #define	_OUT(_field)	offsetof(struct nl_parsed_route, _field)
490 static struct nlattr_parser nla_p_rtmetrics[] = {
491 	{ .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = nlattr_get_uint32 },
492 };
493 NL_DECLARE_ATTR_PARSER(metrics_parser, nla_p_rtmetrics);
494 
495 static const struct nlattr_parser nla_p_rtmsg[] = {
496 	{ .type = NL_RTA_DST, .off = _OUT(rta_dst), .cb = nlattr_get_ip },
497 	{ .type = NL_RTA_OIF, .off = _OUT(rta_oif), .cb = nlattr_get_ifp },
498 	{ .type = NL_RTA_GATEWAY, .off = _OUT(rta_gw), .cb = nlattr_get_ip },
499 	{ .type = NL_RTA_METRICS, .arg = &metrics_parser, .cb = nlattr_get_nested },
500 	{ .type = NL_RTA_MULTIPATH, .off = _OUT(rta_multipath), .cb = nlattr_get_multipath },
501 	{ .type = NL_RTA_WEIGHT, .off = _OUT(rta_weight), .cb = nlattr_get_uint32 },
502 	{ .type = NL_RTA_RTFLAGS, .off = _OUT(rta_rtflags), .cb = nlattr_get_uint32 },
503 	{ .type = NL_RTA_TABLE, .off = _OUT(rta_table), .cb = nlattr_get_uint32 },
504 	{ .type = NL_RTA_VIA, .off = _OUT(rta_gw), .cb = nlattr_get_ipvia },
505 	{ .type = NL_RTA_NH_ID, .off = _OUT(rta_nh_id), .cb = nlattr_get_uint32 },
506 };
507 
508 static const struct nlfield_parser nlf_p_rtmsg[] = {
509 	{ .off_in = _IN(rtm_family), .off_out = _OUT(rtm_family), .cb = nlf_get_u8 },
510 	{ .off_in = _IN(rtm_dst_len), .off_out = _OUT(rtm_dst_len), .cb = nlf_get_u8 },
511 	{ .off_in = _IN(rtm_protocol), .off_out = _OUT(rtm_protocol), .cb = nlf_get_u8 },
512 	{ .off_in = _IN(rtm_type), .off_out = _OUT(rtm_type), .cb = nlf_get_u8 },
513 	{ .off_in = _IN(rtm_flags), .off_out = _OUT(rtm_flags), .cb = nlf_get_u32 },
514 };
515 #undef _IN
516 #undef _OUT
517 
518 static bool
519 post_p_rtmsg(void *_attrs, struct nl_pstate *npt __unused)
520 {
521 	struct nl_parsed_route *attrs = (struct nl_parsed_route *)_attrs;
522 
523 	set_scope6(attrs->rta_dst, attrs->rta_oif);
524 	set_scope6(attrs->rta_gw, attrs->rta_oif);
525 	return (true);
526 }
527 NL_DECLARE_PARSER_EXT(rtm_parser, struct rtmsg, NULL, nlf_p_rtmsg, nla_p_rtmsg, post_p_rtmsg);
528 
529 struct netlink_walkargs {
530 	struct nl_writer *nw;
531 	struct route_nhop_data rnd;
532 	struct nlmsghdr hdr;
533 	struct nlpcb *nlp;
534 	uint32_t fibnum;
535 	int family;
536 	int error;
537 	int count;
538 	int dumped;
539 	int dumped_tables;
540 };
541 
542 static int
543 dump_rtentry(struct rtentry *rt, void *_arg)
544 {
545 	struct netlink_walkargs *wa = (struct netlink_walkargs *)_arg;
546 	int error;
547 
548 	wa->count++;
549 	if (wa->error != 0)
550 		return (0);
551 	if (!rt_is_exportable(rt, nlp_get_cred(wa->nlp)))
552 		return (0);
553 	wa->dumped++;
554 
555 	rt_get_rnd(rt, &wa->rnd);
556 
557 	error = dump_px(wa->fibnum, &wa->hdr, rt, &wa->rnd, wa->nw);
558 
559 	IF_DEBUG_LEVEL(LOG_DEBUG3) {
560 		char rtbuf[INET6_ADDRSTRLEN + 5];
561 		FIB_LOG(LOG_DEBUG3, wa->fibnum, wa->family,
562 		    "Dump %s, offset %u, error %d",
563 		    rt_print_buf(rt, rtbuf, sizeof(rtbuf)),
564 		    wa->nw->offset, error);
565 	}
566 	wa->error = error;
567 
568 	return (0);
569 }
570 
571 static void
572 dump_rtable_one(struct netlink_walkargs *wa, uint32_t fibnum, int family)
573 {
574 	FIB_LOG(LOG_DEBUG2, fibnum, family, "Start dump");
575 	wa->count = 0;
576 	wa->dumped = 0;
577 
578 	rib_walk(fibnum, family, false, dump_rtentry, wa);
579 
580 	wa->dumped_tables++;
581 
582 	FIB_LOG(LOG_DEBUG2, fibnum, family, "End dump, iterated %d dumped %d",
583 	    wa->count, wa->dumped);
584 	NL_LOG(LOG_DEBUG2, "Current offset: %d", wa->nw->offset);
585 }
586 
587 static int
588 dump_rtable_fib(struct netlink_walkargs *wa, uint32_t fibnum, int family)
589 {
590 	wa->fibnum = fibnum;
591 
592 	if (family == AF_UNSPEC) {
593 		for (int i = 0; i < AF_MAX; i++) {
594 			if (rt_tables_get_rnh(fibnum, i) != 0) {
595 				wa->family = i;
596 				dump_rtable_one(wa, fibnum, i);
597 				if (wa->error != 0)
598 					break;
599 			}
600 		}
601 	} else {
602 		if (rt_tables_get_rnh(fibnum, family) != 0) {
603 			wa->family = family;
604 			dump_rtable_one(wa, fibnum, family);
605 		}
606 	}
607 
608 	return (wa->error);
609 }
610 
611 static int
612 handle_rtm_getroute(struct nlpcb *nlp, struct nl_parsed_route *attrs,
613     struct nlmsghdr *hdr, struct nl_pstate *npt)
614 {
615 	RIB_RLOCK_TRACKER;
616 	struct rib_head *rnh;
617 	const struct rtentry *rt;
618 	struct route_nhop_data rnd;
619 	uint32_t fibnum = attrs->rta_table;
620 	sa_family_t family = attrs->rtm_family;
621 
622 	if (attrs->rta_dst == NULL) {
623 		NLMSG_REPORT_ERR_MSG(npt, "No RTA_DST supplied");
624 			return (EINVAL);
625 	}
626 
627 	rnh = rt_tables_get_rnh(fibnum, family);
628 	if (rnh == NULL)
629 		return (EAFNOSUPPORT);
630 
631 	RIB_RLOCK(rnh);
632 
633 	struct sockaddr *dst = attrs->rta_dst;
634 
635 	if (attrs->rtm_flags & RTM_F_PREFIX)
636 		rt = rib_lookup_prefix_plen(rnh, dst, attrs->rtm_dst_len, &rnd);
637 	else
638 		rt = (const struct rtentry *)rnh->rnh_matchaddr(dst, &rnh->head);
639 	if (rt == NULL) {
640 		RIB_RUNLOCK(rnh);
641 		return (ESRCH);
642 	}
643 
644 	rt_get_rnd(rt, &rnd);
645 	rnd.rnd_nhop = nhop_select_func(rnd.rnd_nhop, 0);
646 
647 	RIB_RUNLOCK(rnh);
648 
649 	if (!rt_is_exportable(rt, nlp_get_cred(nlp)))
650 		return (ESRCH);
651 
652 	IF_DEBUG_LEVEL(LOG_DEBUG2) {
653 		char rtbuf[NHOP_PRINT_BUFSIZE] __unused, nhbuf[NHOP_PRINT_BUFSIZE] __unused;
654 		FIB_LOG(LOG_DEBUG2, fibnum, family, "getroute completed: got %s for %s",
655 		    nhop_print_buf_any(rnd.rnd_nhop, nhbuf, sizeof(nhbuf)),
656 		    rt_print_buf(rt, rtbuf, sizeof(rtbuf)));
657 	}
658 
659 	hdr->nlmsg_type = NL_RTM_NEWROUTE;
660 	dump_px(fibnum, hdr, rt, &rnd, npt->nw);
661 
662 	return (0);
663 }
664 
665 static int
666 handle_rtm_dump(struct nlpcb *nlp, uint32_t fibnum, int family,
667     struct nlmsghdr *hdr, struct nl_writer *nw)
668 {
669 	struct netlink_walkargs wa = {
670 		.nlp = nlp,
671 		.nw = nw,
672 		.hdr.nlmsg_pid = hdr->nlmsg_pid,
673 		.hdr.nlmsg_seq = hdr->nlmsg_seq,
674 		.hdr.nlmsg_type = NL_RTM_NEWROUTE,
675 		.hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
676 	};
677 
678 	if (fibnum == RT_TABLE_UNSPEC) {
679 		for (int i = 0; i < V_rt_numfibs; i++) {
680 			dump_rtable_fib(&wa, fibnum, family);
681 			if (wa.error != 0)
682 				break;
683 		}
684 	} else
685 		dump_rtable_fib(&wa, fibnum, family);
686 
687 	if (wa.error == 0 && wa.dumped_tables == 0) {
688 		FIB_LOG(LOG_DEBUG, fibnum, family, "incorrect fibnum/family");
689 		wa.error = ESRCH;
690 		// How do we propagate it?
691 	}
692 
693 	if (!nlmsg_end_dump(wa.nw, wa.error, &wa.hdr)) {
694                 NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
695                 return (ENOMEM);
696         }
697 
698 	return (wa.error);
699 }
700 
701 static struct nhop_object *
702 finalize_nhop(struct nhop_object *nh, const struct sockaddr *dst, int *perror)
703 {
704 	/*
705 	 * The following MUST be filled:
706 	 *  nh_ifp, nh_ifa, nh_gw
707 	 */
708 	if (nh->gw_sa.sa_family == 0) {
709 		/*
710 		 * Empty gateway. Can be direct route with RTA_OIF set.
711 		 */
712 		if (nh->nh_ifp != NULL)
713 			nhop_set_direct_gw(nh, nh->nh_ifp);
714 		else {
715 			NL_LOG(LOG_DEBUG, "empty gateway and interface, skipping");
716 			*perror = EINVAL;
717 			return (NULL);
718 		}
719 		/* Both nh_ifp and gateway are set */
720 	} else {
721 		/* Gateway is set up, we can derive ifp if not set */
722 		if (nh->nh_ifp == NULL) {
723 			uint32_t fibnum = nhop_get_fibnum(nh);
724 			uint32_t flags = 0;
725 
726 			if (nh->nh_flags & NHF_GATEWAY)
727 				flags = RTF_GATEWAY;
728 			else if (nh->nh_flags & NHF_HOST)
729 				flags = RTF_HOST;
730 
731 			struct ifaddr *ifa = ifa_ifwithroute(flags, dst, &nh->gw_sa, fibnum);
732 			if (ifa == NULL) {
733 				NL_LOG(LOG_DEBUG, "Unable to determine ifp, skipping");
734 				*perror = EINVAL;
735 				return (NULL);
736 			}
737 			nhop_set_transmit_ifp(nh, ifa->ifa_ifp);
738 		}
739 	}
740 	/* Both nh_ifp and gateway are set */
741 	if (nh->nh_ifa == NULL) {
742 		const struct sockaddr *gw_sa = &nh->gw_sa;
743 
744 		if (gw_sa->sa_family != dst->sa_family) {
745 			/*
746 			 * Use dst as the target for determining the default
747 			 * preferred ifa IF
748 			 * 1) the gateway is link-level (e.g. direct route)
749 			 * 2) the gateway family is different (e.g. IPv4 over IPv6).
750 			 */
751 			gw_sa = dst;
752 		}
753 
754 		struct ifaddr *ifa = ifaof_ifpforaddr(gw_sa, nh->nh_ifp);
755 		if (ifa == NULL) {
756 			NL_LOG(LOG_DEBUG, "Unable to determine ifa, skipping");
757 			*perror = EINVAL;
758 			return (NULL);
759 		}
760 		nhop_set_src(nh, ifa);
761 	}
762 
763 	return (nhop_get_nhop(nh, perror));
764 }
765 
766 static int
767 get_pxflag(const struct nl_parsed_route *attrs)
768 {
769 	int pxflag = 0;
770 	switch (attrs->rtm_family) {
771 	case AF_INET:
772 		if (attrs->rtm_dst_len == 32)
773 			pxflag = NHF_HOST;
774 		else if (attrs->rtm_dst_len == 0)
775 			pxflag = NHF_DEFAULT;
776 		break;
777 	case AF_INET6:
778 		if (attrs->rtm_dst_len == 128)
779 			pxflag = NHF_HOST;
780 		else if (attrs->rtm_dst_len == 0)
781 			pxflag = NHF_DEFAULT;
782 		break;
783 	}
784 
785 	return (pxflag);
786 }
787 
788 static int
789 get_op_flags(int nlm_flags)
790 {
791 	int op_flags = 0;
792 
793 	op_flags |= (nlm_flags & NLM_F_REPLACE) ? RTM_F_REPLACE : 0;
794 	op_flags |= (nlm_flags & NLM_F_EXCL) ? RTM_F_EXCL : 0;
795 	op_flags |= (nlm_flags & NLM_F_CREATE) ? RTM_F_CREATE : 0;
796 	op_flags |= (nlm_flags & NLM_F_APPEND) ? RTM_F_APPEND : 0;
797 
798 	return (op_flags);
799 }
800 
801 #ifdef ROUTE_MPATH
802 static int
803 create_nexthop_one(struct nl_parsed_route *attrs, struct rta_mpath_nh *mpnh,
804     struct nl_pstate *npt, struct nhop_object **pnh)
805 {
806 	int error;
807 
808 	if (mpnh->gw == NULL)
809 		return (EINVAL);
810 
811 	struct nhop_object *nh = nhop_alloc(attrs->rta_table, attrs->rtm_family);
812 	if (nh == NULL)
813 		return (ENOMEM);
814 
815 	error = nl_set_nexthop_gw(nh, mpnh->gw, mpnh->ifp, npt);
816 	if (error != 0) {
817 		nhop_free(nh);
818 		return (error);
819 	}
820 	if (mpnh->ifp != NULL)
821 		nhop_set_transmit_ifp(nh, mpnh->ifp);
822 	nhop_set_pxtype_flag(nh, get_pxflag(attrs));
823 	nhop_set_rtflags(nh, attrs->rta_rtflags);
824 	if (attrs->rtm_protocol > RTPROT_STATIC)
825 		nhop_set_origin(nh, attrs->rtm_protocol);
826 
827 	*pnh = finalize_nhop(nh, attrs->rta_dst, &error);
828 
829 	return (error);
830 }
831 #endif
832 
833 static struct nhop_object *
834 create_nexthop_from_attrs(struct nl_parsed_route *attrs,
835     struct nl_pstate *npt, int *perror)
836 {
837 	struct nhop_object *nh = NULL;
838 	int error = 0;
839 
840 	if (attrs->rta_multipath != NULL) {
841 #ifdef ROUTE_MPATH
842 		/* Multipath w/o explicit nexthops */
843 		int num_nhops = attrs->rta_multipath->num_nhops;
844 		struct weightened_nhop *wn = npt_alloc(npt, sizeof(*wn) * num_nhops);
845 
846 		for (int i = 0; i < num_nhops; i++) {
847 			struct rta_mpath_nh *mpnh = &attrs->rta_multipath->nhops[i];
848 
849 			error = create_nexthop_one(attrs, mpnh, npt, &wn[i].nh);
850 			if (error != 0) {
851 				for (int j = 0; j < i; j++)
852 					nhop_free(wn[j].nh);
853 				break;
854 			}
855 			wn[i].weight = mpnh->rtnh_weight > 0 ? mpnh->rtnh_weight : 1;
856 		}
857 		if (error == 0) {
858 			struct rib_head *rh = nhop_get_rh(wn[0].nh);
859 			struct nhgrp_object *nhg;
860 
861 			nhg = nhgrp_alloc(rh->rib_fibnum, rh->rib_family,
862 			    wn, num_nhops, perror);
863 			if (nhg != NULL) {
864 				if (attrs->rtm_protocol > RTPROT_STATIC)
865 					nhgrp_set_origin(nhg, attrs->rtm_protocol);
866 				nhg = nhgrp_get_nhgrp(nhg, perror);
867 			}
868 			for (int i = 0; i < num_nhops; i++)
869 				nhop_free(wn[i].nh);
870 			if (nhg != NULL)
871 				return ((struct nhop_object *)nhg);
872 			error = *perror;
873 		}
874 #else
875 		error = ENOTSUP;
876 #endif
877 		*perror = error;
878 	} else {
879 		nh = nhop_alloc(attrs->rta_table, attrs->rtm_family);
880 		if (nh == NULL) {
881 			*perror = ENOMEM;
882 			return (NULL);
883 		}
884 		if (attrs->rta_gw != NULL) {
885 			*perror = nl_set_nexthop_gw(nh, attrs->rta_gw, attrs->rta_oif, npt);
886 			if (*perror != 0) {
887 				nhop_free(nh);
888 				return (NULL);
889 			}
890 		}
891 		if (attrs->rta_oif != NULL)
892 			nhop_set_transmit_ifp(nh, attrs->rta_oif);
893 		if (attrs->rtax_mtu != 0)
894 			nhop_set_mtu(nh, attrs->rtax_mtu, true);
895 		if (attrs->rta_rtflags & RTF_BROADCAST)
896 			nhop_set_broadcast(nh, true);
897 		if (attrs->rtm_protocol > RTPROT_STATIC)
898 			nhop_set_origin(nh, attrs->rtm_protocol);
899 		nhop_set_pxtype_flag(nh, get_pxflag(attrs));
900 		nhop_set_rtflags(nh, attrs->rta_rtflags);
901 
902 		switch (attrs->rtm_type) {
903 		case RTN_UNICAST:
904 			break;
905 		case RTN_BLACKHOLE:
906 			nhop_set_blackhole(nh, RTF_BLACKHOLE);
907 			break;
908 		case RTN_PROHIBIT:
909 		case RTN_UNREACHABLE:
910 			nhop_set_blackhole(nh, RTF_REJECT);
911 			break;
912 		/* TODO: return ENOTSUP for other types if strict option is set */
913 		}
914 
915 		nh = finalize_nhop(nh, attrs->rta_dst, perror);
916 	}
917 
918 	return (nh);
919 }
920 
921 static int
922 rtnl_handle_newroute(struct nlmsghdr *hdr, struct nlpcb *nlp,
923     struct nl_pstate *npt)
924 {
925 	struct rib_cmd_info rc = {};
926 	struct nhop_object *nh = NULL;
927 	int error;
928 
929 	struct nl_parsed_route attrs = {};
930 	error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
931 	if (error != 0)
932 		return (error);
933 
934 	/* Check if we have enough data */
935 	if (attrs.rta_dst == NULL) {
936 		NL_LOG(LOG_DEBUG, "missing RTA_DST");
937 		return (EINVAL);
938 	}
939 
940 	if (attrs.rta_table >= V_rt_numfibs) {
941 		NLMSG_REPORT_ERR_MSG(npt, "invalid fib");
942 		return (EINVAL);
943 	}
944 
945 	if (attrs.rta_nh_id != 0) {
946 		/* Referenced uindex */
947 		int pxflag = get_pxflag(&attrs);
948 		nh = nl_find_nhop(attrs.rta_table, attrs.rtm_family, attrs.rta_nh_id,
949 		    pxflag, &error);
950 		if (error != 0)
951 			return (error);
952 	} else {
953 		nh = create_nexthop_from_attrs(&attrs, npt, &error);
954 		if (error != 0) {
955 			NL_LOG(LOG_DEBUG, "Error creating nexthop");
956 			return (error);
957 		}
958 	}
959 
960 	if (!NH_IS_NHGRP(nh) && attrs.rta_weight == 0)
961 		attrs.rta_weight = RT_DEFAULT_WEIGHT;
962 	struct route_nhop_data rnd = { .rnd_nhop = nh, .rnd_weight = attrs.rta_weight };
963 	int op_flags = get_op_flags(hdr->nlmsg_flags);
964 
965 	error = rib_add_route_px(attrs.rta_table, attrs.rta_dst, attrs.rtm_dst_len,
966 	    &rnd, op_flags, &rc);
967 	if (error == 0)
968 		report_operation(attrs.rta_table, &rc, nlp, hdr);
969 	return (error);
970 }
971 
972 static int
973 path_match_func(const struct rtentry *rt, const struct nhop_object *nh, void *_data)
974 {
975 	struct nl_parsed_route *attrs = (struct nl_parsed_route *)_data;
976 
977 	if ((attrs->rta_gw != NULL) && !rib_match_gw(rt, nh, attrs->rta_gw))
978 		return (0);
979 
980 	if ((attrs->rta_oif != NULL) && (attrs->rta_oif != nh->nh_ifp))
981 		return (0);
982 
983 	return (1);
984 }
985 
986 static int
987 rtnl_handle_delroute(struct nlmsghdr *hdr, struct nlpcb *nlp,
988     struct nl_pstate *npt)
989 {
990 	struct rib_cmd_info rc;
991 	int error;
992 
993 	struct nl_parsed_route attrs = {};
994 	error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
995 	if (error != 0)
996 		return (error);
997 
998 	if (attrs.rta_dst == NULL) {
999 		NLMSG_REPORT_ERR_MSG(npt, "RTA_DST is not set");
1000 		return (ESRCH);
1001 	}
1002 
1003 	if (attrs.rta_table >= V_rt_numfibs) {
1004 		NLMSG_REPORT_ERR_MSG(npt, "invalid fib");
1005 		return (EINVAL);
1006 	}
1007 
1008 	error = rib_del_route_px(attrs.rta_table, attrs.rta_dst,
1009 	    attrs.rtm_dst_len, path_match_func, &attrs, 0, &rc);
1010 	if (error == 0)
1011 		report_operation(attrs.rta_table, &rc, nlp, hdr);
1012 	return (error);
1013 }
1014 
1015 static int
1016 rtnl_handle_getroute(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
1017 {
1018 	int error;
1019 
1020 	struct nl_parsed_route attrs = {};
1021 	error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
1022 	if (error != 0)
1023 		return (error);
1024 
1025 	if (attrs.rta_table >= V_rt_numfibs) {
1026 		NLMSG_REPORT_ERR_MSG(npt, "invalid fib");
1027 		return (EINVAL);
1028 	}
1029 
1030 	if (hdr->nlmsg_flags & NLM_F_DUMP)
1031 		error = handle_rtm_dump(nlp, attrs.rta_table, attrs.rtm_family, hdr, npt->nw);
1032 	else
1033 		error = handle_rtm_getroute(nlp, &attrs, hdr, npt);
1034 
1035 	return (error);
1036 }
1037 
1038 void
1039 rtnl_handle_route_event(uint32_t fibnum, const struct rib_cmd_info *rc)
1040 {
1041 	struct nl_writer nw = {};
1042 	int family, nlm_flags = 0;
1043 
1044 	family = rt_get_family(rc->rc_rt);
1045 
1046 	/* XXX: check if there are active listeners first */
1047 
1048 	/* TODO: consider passing PID/type/seq */
1049 	switch (rc->rc_cmd) {
1050 	case RTM_ADD:
1051 		nlm_flags = NLM_F_EXCL | NLM_F_CREATE;
1052 		break;
1053 	case RTM_CHANGE:
1054 		nlm_flags = NLM_F_REPLACE;
1055 		break;
1056 	case RTM_DELETE:
1057 		nlm_flags = 0;
1058 		break;
1059 	}
1060 	IF_DEBUG_LEVEL(LOG_DEBUG2) {
1061 		char rtbuf[NHOP_PRINT_BUFSIZE] __unused;
1062 		FIB_LOG(LOG_DEBUG2, fibnum, family,
1063 		    "received event %s for %s / nlm_flags=%X",
1064 		    rib_print_cmd(rc->rc_cmd),
1065 		    rt_print_buf(rc->rc_rt, rtbuf, sizeof(rtbuf)),
1066 		    nlm_flags);
1067 	}
1068 
1069 	struct nlmsghdr hdr = {
1070 		.nlmsg_flags = nlm_flags,
1071 		.nlmsg_type = get_rtmsg_type_from_rtsock(rc->rc_cmd),
1072 	};
1073 
1074 	struct route_nhop_data rnd = {
1075 		.rnd_nhop = rc_get_nhop(rc),
1076 		.rnd_weight = rc->rc_nh_weight,
1077 	};
1078 
1079 	uint32_t group_id = family_to_group(family);
1080 	if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, group_id)) {
1081 		NL_LOG(LOG_DEBUG, "error allocating event buffer");
1082 		return;
1083 	}
1084 
1085 	dump_px(fibnum, &hdr, rc->rc_rt, &rnd, &nw);
1086 	nlmsg_flush(&nw);
1087 }
1088 
1089 static const struct rtnl_cmd_handler cmd_handlers[] = {
1090 	{
1091 		.cmd = NL_RTM_GETROUTE,
1092 		.name = "RTM_GETROUTE",
1093 		.cb = &rtnl_handle_getroute,
1094 		.flags = RTNL_F_ALLOW_NONVNET_JAIL,
1095 	},
1096 	{
1097 		.cmd = NL_RTM_DELROUTE,
1098 		.name = "RTM_DELROUTE",
1099 		.cb = &rtnl_handle_delroute,
1100 		.priv = PRIV_NET_ROUTE,
1101 	},
1102 	{
1103 		.cmd = NL_RTM_NEWROUTE,
1104 		.name = "RTM_NEWROUTE",
1105 		.cb = &rtnl_handle_newroute,
1106 		.priv = PRIV_NET_ROUTE,
1107 	}
1108 };
1109 
1110 static const struct nlhdr_parser *all_parsers[] = {&mpath_parser, &metrics_parser, &rtm_parser};
1111 
1112 void
1113 rtnl_routes_init(void)
1114 {
1115 	NL_VERIFY_PARSERS(all_parsers);
1116 	rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
1117 }
1118