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