xref: /freebsd/sys/netlink/route/neigh.c (revision ea9075959bf3a6e98d9638467500a690267f3947)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include "opt_netlink.h"
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 #include "opt_inet.h"
33 #include "opt_inet6.h"
34 #include <sys/types.h>
35 #include <sys/eventhandler.h>
36 #include <sys/kernel.h>
37 #include <sys/malloc.h>
38 #include <sys/socket.h>
39 #include <sys/syslog.h>
40 
41 #include <net/if.h>
42 #include <net/if_llatbl.h>
43 #include <netlink/netlink.h>
44 #include <netlink/netlink_ctl.h>
45 #include <netlink/netlink_route.h>
46 #include <netlink/route/route_var.h>
47 
48 #include <netinet6/in6_var.h>		/* nd6.h requires this */
49 #include <netinet6/nd6.h>		/* nd6 state machine */
50 #include <netinet6/scope6_var.h>	/* scope deembedding */
51 
52 #define	DEBUG_MOD_NAME	nl_neigh
53 #define	DEBUG_MAX_LEVEL	LOG_DEBUG3
54 #include <netlink/netlink_debug.h>
55 _DECLARE_DEBUG(LOG_DEBUG);
56 
57 static int lle_families[] = { AF_INET, AF_INET6 };
58 
59 static eventhandler_tag lle_event_p;
60 
61 struct netlink_walkargs {
62 	struct nl_writer *nw;
63 	struct nlmsghdr hdr;
64 	struct nlpcb *so;
65 	struct ifnet *ifp;
66 	int family;
67 	int error;
68 	int count;
69 	int dumped;
70 };
71 
72 static int
73 lle_state_to_nl_state(int family, struct llentry *lle)
74 {
75 	int state = lle->ln_state;
76 
77 	switch (family) {
78 	case AF_INET:
79 		if (lle->la_flags & (LLE_STATIC | LLE_IFADDR))
80 			state = 1;
81 		switch (state) {
82 		case 0: /* ARP_LLINFO_INCOMPLETE */
83 			return (NUD_INCOMPLETE);
84 		case 1: /* ARP_LLINFO_REACHABLE  */
85 			return (NUD_REACHABLE);
86 		case 2: /* ARP_LLINFO_VERIFY */
87 			return (NUD_PROBE);
88 		}
89 		break;
90 	case AF_INET6:
91 		switch (state) {
92 		case ND6_LLINFO_INCOMPLETE:
93 			return (NUD_INCOMPLETE);
94 		case ND6_LLINFO_REACHABLE:
95 			return (NUD_REACHABLE);
96 		case ND6_LLINFO_STALE:
97 			return (NUD_STALE);
98 		case ND6_LLINFO_DELAY:
99 			return (NUD_DELAY);
100 		case ND6_LLINFO_PROBE:
101 			return (NUD_PROBE);
102 		}
103 		break;
104 	}
105 
106 	return (NUD_NONE);
107 }
108 
109 static uint32_t
110 lle_flags_to_nl_flags(const struct llentry *lle)
111 {
112 	uint32_t nl_flags = 0;
113 
114 	if (lle->la_flags & LLE_IFADDR)
115 		nl_flags |= NTF_SELF;
116 	if (lle->la_flags & LLE_PUB)
117 		nl_flags |= NTF_PROXY;
118 	if (lle->la_flags & LLE_STATIC)
119 		nl_flags |= NTF_STICKY;
120 	if (lle->ln_router != 0)
121 		nl_flags |= NTF_ROUTER;
122 
123 	return (nl_flags);
124 }
125 
126 static uint32_t
127 get_lle_next_ts(const struct llentry *lle)
128 {
129 	if (lle->la_expire == 0)
130 		return (0);
131 	return (lle->la_expire + lle->lle_remtime / hz + time_second - time_uptime);
132 }
133 
134 static int
135 dump_lle_locked(struct llentry *lle, void *arg)
136 {
137 	struct netlink_walkargs *wa = (struct netlink_walkargs *)arg;
138 	struct nlmsghdr *hdr = &wa->hdr;
139 	struct nl_writer *nw = wa->nw;
140 	struct ndmsg *ndm;
141 #if defined(INET) || defined(INET6)
142 	union {
143 		struct in_addr	in;
144 		struct in6_addr	in6;
145 	} addr;
146 #endif
147 
148 	IF_DEBUG_LEVEL(LOG_DEBUG2) {
149 		char llebuf[NHOP_PRINT_BUFSIZE];
150 		llentry_print_buf_lltable(lle, llebuf, sizeof(llebuf));
151 		NL_LOG(LOG_DEBUG2, "dumping %s", llebuf);
152 	}
153 
154 	if (!nlmsg_reply(nw, hdr, sizeof(struct ndmsg)))
155 		goto enomem;
156 
157 	ndm = nlmsg_reserve_object(nw, struct ndmsg);
158 	ndm->ndm_family = wa->family;
159 	ndm->ndm_pad1 = 0;
160 	ndm->ndm_pad2 = 0;
161 	ndm->ndm_ifindex = wa->ifp->if_index;
162 	ndm->ndm_state = lle_state_to_nl_state(wa->family, lle);
163 	ndm->ndm_flags = lle_flags_to_nl_flags(lle);
164 	ndm->ndm_type = 0;
165 
166 	switch (wa->family) {
167 #ifdef INET
168 	case AF_INET:
169 		addr.in = lle->r_l3addr.addr4;
170 		nlattr_add(nw, NDA_DST, 4, &addr);
171 		break;
172 #endif
173 #ifdef INET6
174 	case AF_INET6:
175 		addr.in6 = lle->r_l3addr.addr6;
176 		in6_clearscope(&addr.in6);
177 		nlattr_add(nw, NDA_DST, 16, &addr);
178 		break;
179 #endif
180 	}
181 
182 	if (lle->r_flags & RLLE_VALID) {
183 		/* Has L2 */
184 		int addrlen = wa->ifp->if_addrlen;
185 		nlattr_add(nw, NDA_LLADDR, addrlen, lle->ll_addr);
186 	}
187 
188 	nlattr_add_u32(nw, NDA_PROBES, lle->la_asked);
189 
190 	struct nda_cacheinfo *cache;
191 	cache = nlmsg_reserve_attr(nw, NDA_CACHEINFO, struct nda_cacheinfo);
192 	if (cache == NULL)
193 		goto enomem;
194 	/* TODO: provide confirmed/updated */
195 	cache->ndm_refcnt = lle->lle_refcnt;
196 
197 	int off = nlattr_add_nested(nw, NDA_FREEBSD);
198 	if (off != 0) {
199 		nlattr_add_u32(nw, NDAF_NEXT_STATE_TS, get_lle_next_ts(lle));
200 
201 		nlattr_set_len(nw, off);
202 	}
203 
204         if (nlmsg_end(nw))
205 		return (0);
206 enomem:
207         NL_LOG(LOG_DEBUG, "unable to dump lle state (ENOMEM)");
208         nlmsg_abort(nw);
209         return (ENOMEM);
210 }
211 
212 static int
213 dump_lle(struct lltable *llt, struct llentry *lle, void *arg)
214 {
215 	int error;
216 
217 	LLE_RLOCK(lle);
218 	error = dump_lle_locked(lle, arg);
219 	LLE_RUNLOCK(lle);
220 	return (error);
221 }
222 
223 static bool
224 dump_llt(struct lltable *llt, struct netlink_walkargs *wa)
225 {
226 	lltable_foreach_lle(llt, dump_lle, wa);
227 
228 	return (true);
229 }
230 
231 static int
232 dump_llts_iface(struct netlink_walkargs *wa, struct ifnet *ifp, int family)
233 {
234 	int error = 0;
235 
236 	wa->ifp = ifp;
237 	for (int i = 0; i < sizeof(lle_families) / sizeof(int); i++) {
238 		int fam = lle_families[i];
239 		struct lltable *llt = lltable_get(ifp, fam);
240 		if (llt != NULL && (family == 0 || family == fam)) {
241 			wa->count++;
242 			wa->family = fam;
243 			if (!dump_llt(llt, wa)) {
244 				error = ENOMEM;
245 				break;
246 			}
247 			wa->dumped++;
248 		}
249 	}
250 	return (error);
251 }
252 
253 static int
254 dump_llts(struct netlink_walkargs *wa, struct ifnet *ifp, int family)
255 {
256 	NL_LOG(LOG_DEBUG, "Start dump ifp=%s family=%d", ifp ? if_name(ifp) : "NULL", family);
257 
258 	wa->hdr.nlmsg_flags |= NLM_F_MULTI;
259 
260 	if (ifp != NULL) {
261 		dump_llts_iface(wa, ifp, family);
262 	} else {
263 		CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
264 			dump_llts_iface(wa, ifp, family);
265 		}
266 	}
267 
268 	NL_LOG(LOG_DEBUG, "End dump, iterated %d dumped %d", wa->count, wa->dumped);
269 
270 	if (!nlmsg_end_dump(wa->nw, wa->error, &wa->hdr)) {
271                 NL_LOG(LOG_DEBUG, "Unable to add new message");
272                 return (ENOMEM);
273         }
274 
275 	return (0);
276 }
277 
278 static int
279 get_lle(struct netlink_walkargs *wa, struct ifnet *ifp, int family, struct sockaddr *dst)
280 {
281 	struct lltable *llt = lltable_get(ifp, family);
282 	if (llt == NULL)
283 		return (ESRCH);
284 
285 #ifdef INET6
286 	if (dst->sa_family == AF_INET6) {
287 		struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst;
288 
289 		if (IN6_IS_SCOPE_LINKLOCAL(&dst6->sin6_addr))
290 			in6_set_unicast_scopeid(&dst6->sin6_addr, ifp->if_index);
291 	}
292 #endif
293 	struct llentry *lle = lla_lookup(llt, LLE_UNLOCKED, dst);
294 	if (lle == NULL)
295 		return (ESRCH);
296 
297 	wa->ifp = ifp;
298 	wa->family = family;
299 
300 	return (dump_lle(llt, lle, wa));
301 }
302 
303 struct nl_parsed_neigh {
304 	struct sockaddr	*nda_dst;
305 	struct ifnet	*nda_ifp;
306 	struct nlattr	*nda_lladdr;
307 	uint32_t	ndaf_next_ts;
308 	uint32_t	ndm_flags;
309 	uint16_t	ndm_state;
310 	uint8_t		ndm_family;
311 };
312 
313 #define	_IN(_field)	offsetof(struct ndmsg, _field)
314 #define	_OUT(_field)	offsetof(struct nl_parsed_neigh, _field)
315 static const struct nlattr_parser nla_p_neigh_fbsd[] = {
316 	{ .type = NDAF_NEXT_STATE_TS, .off = _OUT(ndaf_next_ts), .cb = nlattr_get_uint32 },
317 };
318 NL_DECLARE_ATTR_PARSER(neigh_fbsd_parser, nla_p_neigh_fbsd);
319 
320 static const struct nlfield_parser nlf_p_neigh[] = {
321 	{ .off_in = _IN(ndm_family), .off_out = _OUT(ndm_family), .cb = nlf_get_u8 },
322 	{ .off_in = _IN(ndm_flags), .off_out = _OUT(ndm_flags), .cb = nlf_get_u8_u32 },
323 	{ .off_in = _IN(ndm_state), .off_out = _OUT(ndm_state), .cb = nlf_get_u16 },
324 	{ .off_in = _IN(ndm_ifindex), .off_out = _OUT(nda_ifp), .cb = nlf_get_ifpz },
325 };
326 
327 static const struct nlattr_parser nla_p_neigh[] = {
328 	{ .type = NDA_DST, .off = _OUT(nda_dst), .cb = nlattr_get_ip },
329 	{ .type = NDA_LLADDR, .off = _OUT(nda_lladdr), .cb = nlattr_get_nla },
330 	{ .type = NDA_IFINDEX, .off = _OUT(nda_ifp), .cb = nlattr_get_ifp },
331 	{ .type = NDA_FLAGS_EXT, .off = _OUT(ndm_flags), .cb = nlattr_get_uint32 },
332 	{ .type = NDA_FREEBSD, .arg = &neigh_fbsd_parser, .cb = nlattr_get_nested },
333 };
334 #undef _IN
335 #undef _OUT
336 NL_DECLARE_PARSER(ndmsg_parser, struct ndmsg, nlf_p_neigh, nla_p_neigh);
337 
338 
339 /*
340  * type=RTM_NEWNEIGH, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1661941473, pid=0},
341  * {ndm_family=AF_INET6, ndm_ifindex=if_nametoindex("enp0s31f6"), ndm_state=NUD_PERMANENT, ndm_flags=0, ndm_type=RTN_UNSPEC},
342  * [
343  *  {{nla_len=20, nla_type=NDA_DST}, inet_pton(AF_INET6, "2a01:4f8:13a:70c::3")},
344  *  {{nla_len=10, nla_type=NDA_LLADDR}, 20:4e:71:62:ae:f2}]}, iov_len=60}
345  */
346 
347 static int
348 rtnl_handle_newneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
349 {
350 	int error;
351 
352 	struct nl_parsed_neigh attrs = {};
353 	error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
354 	if (error != 0)
355 		return (error);
356 
357 	if (attrs.nda_ifp == NULL || attrs.nda_dst == NULL || attrs.nda_lladdr == NULL) {
358 		if (attrs.nda_ifp == NULL)
359 			NLMSG_REPORT_ERR_MSG(npt, "NDA_IFINDEX / ndm_ifindex not set");
360 		if (attrs.nda_dst == NULL)
361 			NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set");
362 		if (attrs.nda_lladdr == NULL)
363 			NLMSG_REPORT_ERR_MSG(npt, "NDA_LLADDR not set");
364 		return (EINVAL);
365 	}
366 
367 	if (attrs.nda_dst->sa_family != attrs.ndm_family) {
368 		NLMSG_REPORT_ERR_MSG(npt,
369 		    "NDA_DST family (%d) is different from ndm_family (%d)",
370 		    attrs.nda_dst->sa_family, attrs.ndm_family);
371 		return (EINVAL);
372 	}
373 
374 	int addrlen = attrs.nda_ifp->if_addrlen;
375 	if (attrs.nda_lladdr->nla_len != sizeof(struct nlattr) + addrlen) {
376 		NLMSG_REPORT_ERR_MSG(npt,
377 		    "NDA_LLADDR address length (%d) is different from expected (%d)",
378 		    (int)attrs.nda_lladdr->nla_len - (int)sizeof(struct nlattr), addrlen);
379 		return (EINVAL);
380 	}
381 
382 	const uint16_t supported_flags = NTF_PROXY | NTF_STICKY;
383 	if ((attrs.ndm_flags & supported_flags) != attrs.ndm_flags) {
384 		NLMSG_REPORT_ERR_MSG(npt, "ndm_flags %X not supported",
385 		    attrs.ndm_flags &~ supported_flags);
386 		return (ENOTSUP);
387 	}
388 
389 	/* Replacement requires new entry creation anyway */
390 	if ((hdr->nlmsg_flags & (NLM_F_CREATE | NLM_F_REPLACE)) == 0)
391 		return (ENOTSUP);
392 
393 	struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family);
394 	if (llt == NULL)
395 		return (EAFNOSUPPORT);
396 
397 
398 	uint8_t linkhdr[LLE_MAX_LINKHDR];
399 	size_t linkhdrsize = sizeof(linkhdr);
400 	int lladdr_off = 0;
401 	if (lltable_calc_llheader(attrs.nda_ifp, attrs.ndm_family,
402 	    (char *)(attrs.nda_lladdr + 1), linkhdr, &linkhdrsize, &lladdr_off) != 0) {
403 		NLMSG_REPORT_ERR_MSG(npt, "unable to calculate lle prepend data");
404 		return (EINVAL);
405 	}
406 
407 	int lle_flags = (attrs.ndm_flags & NTF_PROXY) ? LLE_PUB : 0;
408 	if (attrs.ndm_flags & NTF_STICKY)
409 		lle_flags |= LLE_STATIC;
410 	struct llentry *lle = lltable_alloc_entry(llt, lle_flags, attrs.nda_dst);
411 	if (lle == NULL)
412 		return (ENOMEM);
413 	lltable_set_entry_addr(attrs.nda_ifp, lle, linkhdr, linkhdrsize, lladdr_off);
414 
415 	if (attrs.ndm_flags & NTF_STICKY)
416 		lle->la_expire = 0;
417 	else
418 		lle->la_expire = attrs.ndaf_next_ts - time_second + time_uptime;
419 
420 	/* llentry created, try to insert or update */
421 	IF_AFDATA_WLOCK(attrs.nda_ifp);
422 	LLE_WLOCK(lle);
423 	struct llentry *lle_tmp = lla_lookup(llt, LLE_EXCLUSIVE, attrs.nda_dst);
424 	if (lle_tmp != NULL) {
425 		error = EEXIST;
426 		if (hdr->nlmsg_flags & NLM_F_EXCL) {
427 			LLE_WUNLOCK(lle_tmp);
428 			lle_tmp = NULL;
429 		} else if (hdr->nlmsg_flags & NLM_F_REPLACE) {
430 			if ((lle_tmp->la_flags & LLE_IFADDR) == 0) {
431 				lltable_unlink_entry(llt, lle_tmp);
432 				lltable_link_entry(llt, lle);
433 				error = 0;
434 			} else
435 				error = EPERM;
436 		}
437 	} else {
438 		if (hdr->nlmsg_flags & NLM_F_CREATE)
439 			lltable_link_entry(llt, lle);
440 		else
441 			error = ENOENT;
442 	}
443 	IF_AFDATA_WUNLOCK(attrs.nda_ifp);
444 
445 	if (error != 0) {
446 		if (lle != NULL)
447 			llentry_free(lle);
448 		return (error);
449 	}
450 
451 	if (lle_tmp != NULL)
452 		llentry_free(lle_tmp);
453 
454 	/* XXX: We're inside epoch */
455 	EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_RESOLVED);
456 	LLE_WUNLOCK(lle);
457 	llt->llt_post_resolved(llt, lle);
458 
459 	return (0);
460 }
461 
462 static int
463 rtnl_handle_delneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
464 {
465 	int error;
466 
467 	struct nl_parsed_neigh attrs = {};
468 	error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
469 	if (error != 0)
470 		return (error);
471 
472 	if (attrs.nda_dst == NULL) {
473 		NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set");
474 		return (EINVAL);
475 	}
476 
477 	if (attrs.nda_ifp == NULL) {
478 		NLMSG_REPORT_ERR_MSG(npt, "no ifindex provided");
479 		return (EINVAL);
480 	}
481 
482 	struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family);
483 	if (llt == NULL)
484 		return (EAFNOSUPPORT);
485 
486 	return (lltable_delete_addr(llt, 0, attrs.nda_dst));
487 }
488 
489 static int
490 rtnl_handle_getneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
491 {
492 	int error;
493 
494 	struct nl_parsed_neigh attrs = {};
495 	error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
496 	if (error != 0)
497 		return (error);
498 
499 	if (attrs.nda_dst != NULL && attrs.nda_ifp == NULL) {
500 		NLMSG_REPORT_ERR_MSG(npt, "has NDA_DST but no ifindex provided");
501 		return (EINVAL);
502 	}
503 
504 	struct netlink_walkargs wa = {
505 		.so = nlp,
506 		.nw = npt->nw,
507 		.hdr.nlmsg_pid = hdr->nlmsg_pid,
508 		.hdr.nlmsg_seq = hdr->nlmsg_seq,
509 		.hdr.nlmsg_flags = hdr->nlmsg_flags,
510 		.hdr.nlmsg_type = NL_RTM_NEWNEIGH,
511 	};
512 
513 	if (attrs.nda_dst == NULL)
514 		error = dump_llts(&wa, attrs.nda_ifp, attrs.ndm_family);
515 	else
516 		error = get_lle(&wa, attrs.nda_ifp, attrs.ndm_family, attrs.nda_dst);
517 
518 	return (error);
519 }
520 
521 static const struct rtnl_cmd_handler cmd_handlers[] = {
522 	{
523 		.cmd = NL_RTM_NEWNEIGH,
524 		.name = "RTM_NEWNEIGH",
525 		.cb = &rtnl_handle_newneigh,
526 		.priv = PRIV_NET_ROUTE,
527 	},
528 	{
529 		.cmd = NL_RTM_DELNEIGH,
530 		.name = "RTM_DELNEIGH",
531 		.cb = &rtnl_handle_delneigh,
532 		.priv = PRIV_NET_ROUTE,
533 	},
534 	{
535 		.cmd = NL_RTM_GETNEIGH,
536 		.name = "RTM_GETNEIGH",
537 		.cb = &rtnl_handle_getneigh,
538 	}
539 };
540 
541 static void
542 rtnl_lle_event(void *arg __unused, struct llentry *lle, int evt)
543 {
544 	struct ifnet *ifp;
545 	int family;
546 
547 	LLE_WLOCK_ASSERT(lle);
548 
549 	ifp = lltable_get_ifp(lle->lle_tbl);
550 	family = lltable_get_af(lle->lle_tbl);
551 
552 	if (family != AF_INET && family != AF_INET6)
553 		return;
554 
555 	int nlmsgs_type = evt == LLENTRY_RESOLVED ? NL_RTM_NEWNEIGH : NL_RTM_DELNEIGH;
556 
557 	struct nl_writer nw = {};
558 	if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEIGH)) {
559 		NL_LOG(LOG_DEBUG, "error allocating group writer");
560 		return;
561 	}
562 
563 	struct netlink_walkargs wa = {
564 		.hdr.nlmsg_type = nlmsgs_type,
565 		.nw = &nw,
566 		.ifp = ifp,
567 		.family = family,
568 	};
569 
570 	dump_lle_locked(lle, &wa);
571 	nlmsg_flush(&nw);
572 }
573 
574 static const struct nlhdr_parser *all_parsers[] = { &ndmsg_parser, &neigh_fbsd_parser };
575 
576 void
577 rtnl_neighs_init(void)
578 {
579 	NL_VERIFY_PARSERS(all_parsers);
580 	rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
581 	lle_event_p = EVENTHANDLER_REGISTER(lle_event, rtnl_lle_event, NULL,
582 	    EVENTHANDLER_PRI_ANY);
583 }
584 
585 void
586 rtnl_neighs_destroy(void)
587 {
588 	EVENTHANDLER_DEREGISTER(lle_event, lle_event_p);
589 }
590