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