Changeset View
Changeset View
Standalone View
Standalone View
sys/netlink/route/neigh.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2022 Alexander V. Chernikov | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include "opt_inet.h" | |||||
#include "opt_inet6.h" | |||||
#include <sys/types.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/syslog.h> | |||||
#include <net/if.h> | |||||
#include <net/if_llatbl.h> | |||||
#include <netlink/netlink.h> | |||||
#include <netlink/netlink_ctl.h> | |||||
#include <netlink/netlink_route.h> | |||||
#include <netlink/route/route_var.h> | |||||
#include <netinet6/in6_var.h> /* nd6.h requires this */ | |||||
#include <netinet6/nd6.h> /* nd6 state machine */ | |||||
#include <netinet6/scope6_var.h> /* scope deembedding */ | |||||
#define DEBUG_MOD_NAME nl_neigh | |||||
#define DEBUG_MAX_LEVEL LOG_DEBUG3 | |||||
#include <netlink/netlink_debug.h> | |||||
_DECLARE_DEBUG(LOG_DEBUG); | |||||
static int lle_families[] = { AF_INET, AF_INET6 }; | |||||
static eventhandler_tag lle_event_p; | |||||
struct netlink_walkargs { | |||||
struct nlmsg_state *ns; | |||||
struct nlmsghdr hdr; | |||||
struct nlpcb *so; | |||||
struct ifnet *ifp; | |||||
int family; | |||||
int error; | |||||
int count; | |||||
int dumped; | |||||
}; | |||||
static int | |||||
lle_state_to_nl_state(int family, struct llentry *lle) | |||||
{ | |||||
int state = lle->ln_state; | |||||
switch (family) { | |||||
case AF_INET: | |||||
if (lle->la_flags & (LLE_STATIC | LLE_IFADDR)) | |||||
state = 1; | |||||
switch (state) { | |||||
case 0: /* ARP_LLINFO_INCOMPLETE */ | |||||
return (NUD_INCOMPLETE); | |||||
case 1: /* ARP_LLINFO_REACHABLE */ | |||||
return (NUD_REACHABLE); | |||||
case 2: /* ARP_LLINFO_VERIFY */ | |||||
return (NUD_PROBE); | |||||
} | |||||
break; | |||||
case AF_INET6: | |||||
switch (state) { | |||||
case ND6_LLINFO_INCOMPLETE: | |||||
return (NUD_INCOMPLETE); | |||||
case ND6_LLINFO_REACHABLE: | |||||
return (NUD_REACHABLE); | |||||
case ND6_LLINFO_STALE: | |||||
return (NUD_STALE); | |||||
case ND6_LLINFO_DELAY: | |||||
return (NUD_DELAY); | |||||
case ND6_LLINFO_PROBE: | |||||
return (NUD_PROBE); | |||||
} | |||||
break; | |||||
} | |||||
return (NUD_NONE); | |||||
} | |||||
static uint32_t | |||||
lle_flags_to_nl_flags(const struct llentry *lle) | |||||
{ | |||||
uint32_t nl_flags = 0; | |||||
if (lle->la_flags & LLE_IFADDR) | |||||
nl_flags |= NTF_SELF; | |||||
if (lle->la_flags & LLE_PUB) | |||||
nl_flags |= NTF_PROXY; | |||||
if (lle->la_flags & LLE_STATIC) | |||||
nl_flags |= NTF_STICKY; | |||||
if (lle->ln_router != 0) | |||||
nl_flags |= NTF_ROUTER; | |||||
return (nl_flags); | |||||
} | |||||
static int | |||||
dump_lle_locked(struct lltable *llt, struct llentry *lle, void *arg) | |||||
{ | |||||
struct netlink_walkargs *wa = (struct netlink_walkargs *)arg; | |||||
struct nlmsghdr *hdr = &wa->hdr; | |||||
struct nlmsg_state *ns = wa->ns; | |||||
struct ndmsg *ndm; | |||||
union { | |||||
struct in_addr in; | |||||
struct in6_addr in6; | |||||
} addr; | |||||
IF_DEBUG_LEVEL(LOG_DEBUG2) { | |||||
char llebuf[NHOP_PRINT_BUFSIZE]; | |||||
llentry_print_buf_lltable(lle, llebuf, sizeof(llebuf)); | |||||
NL_LOG(LOG_DEBUG2, "dumping %s", llebuf); | |||||
} | |||||
if (!nlmsg_reply(ns, hdr, sizeof(struct ndmsg))) | |||||
goto enomem; | |||||
ndm = nlmsg_reserve_object(ns, struct ndmsg); | |||||
ndm->ndm_family = wa->family; | |||||
ndm->ndm_ifindex = wa->ifp->if_index; | |||||
ndm->ndm_state = lle_state_to_nl_state(wa->family, lle); | |||||
ndm->ndm_flags = lle_flags_to_nl_flags(lle); | |||||
switch (wa->family) { | |||||
#ifdef INET | |||||
case AF_INET: | |||||
addr.in = lle->r_l3addr.addr4; | |||||
nlattr_add(ns, NDA_DST, 4, &addr); | |||||
break; | |||||
#endif | |||||
#ifdef INET6 | |||||
case AF_INET6: | |||||
addr.in6 = lle->r_l3addr.addr6; | |||||
in6_clearscope(&addr.in6); | |||||
nlattr_add(ns, NDA_DST, 16, &addr); | |||||
break; | |||||
#endif | |||||
} | |||||
if (lle->r_flags & RLLE_VALID) { | |||||
/* Has L2 */ | |||||
int addrlen = wa->ifp->if_addrlen; | |||||
nlattr_add(ns, NDA_LLADDR, addrlen, lle->ll_addr); | |||||
} | |||||
nlattr_add_u32(ns, NDA_PROBES, lle->la_asked); | |||||
struct nda_cacheinfo *cache; | |||||
cache = nlmsg_reserve_attr(ns, NDA_CACHEINFO, struct nda_cacheinfo); | |||||
if (cache == NULL) | |||||
goto enomem; | |||||
/* TODO: provide confirmed/updated */ | |||||
cache->ndm_refcnt = lle->lle_refcnt; | |||||
if (nlmsg_end(ns)) | |||||
return (0); | |||||
enomem: | |||||
NL_LOG(LOG_DEBUG, "unable to dump lle state (ENOMEM)"); | |||||
nlmsg_abort(ns); | |||||
return (ENOMEM); | |||||
} | |||||
static int | |||||
dump_lle(struct lltable *llt, struct llentry *lle, void *arg) | |||||
{ | |||||
int error; | |||||
LLE_RLOCK(lle); | |||||
error = dump_lle_locked(llt, lle, arg); | |||||
LLE_RUNLOCK(lle); | |||||
return (error); | |||||
} | |||||
static bool | |||||
dump_llt(struct lltable *llt, struct netlink_walkargs *wa) | |||||
{ | |||||
lltable_foreach_lle(llt, dump_lle, wa); | |||||
return (true); | |||||
} | |||||
static int | |||||
dump_llts_iface(struct netlink_walkargs *wa, struct ifnet *ifp, int family) | |||||
{ | |||||
int error = 0; | |||||
wa->ifp = ifp; | |||||
for (int i = 0; i < sizeof(lle_families) / sizeof(int); i++) { | |||||
int fam = lle_families[i]; | |||||
struct lltable *llt = lltable_get(ifp, fam); | |||||
if (llt != NULL && (family == 0 || family == fam)) { | |||||
wa->count++; | |||||
wa->family = fam; | |||||
if (!dump_llt(llt, wa)) { | |||||
error = ENOMEM; | |||||
break; | |||||
} | |||||
wa->dumped++; | |||||
} | |||||
} | |||||
return (error); | |||||
} | |||||
static int | |||||
dump_llts(struct netlink_walkargs *wa, struct ifnet *ifp, int family) | |||||
{ | |||||
NL_LOG(LOG_DEBUG, "Start dump ifp=%s family=%d", ifp ? if_name(ifp) : "NULL", family); | |||||
wa->hdr.nlmsg_flags |= NLM_F_MULTI; | |||||
if (ifp != NULL) { | |||||
dump_llts_iface(wa, ifp, family); | |||||
} else { | |||||
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { | |||||
dump_llts_iface(wa, ifp, family); | |||||
} | |||||
} | |||||
NL_LOG(LOG_DEBUG, "End dump, iterated %d dumped %d", wa->count, wa->dumped); | |||||
if (!nlmsg_end_dump(wa->ns, wa->error, &wa->hdr)) { | |||||
NL_LOG(LOG_DEBUG, "Unable to add new message"); | |||||
return (ENOMEM); | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
get_lle(struct netlink_walkargs *wa, struct ifnet *ifp, int family, struct sockaddr *dst) | |||||
{ | |||||
struct lltable *llt = lltable_get(ifp, family); | |||||
if (llt == NULL) | |||||
return (ESRCH); | |||||
#ifdef INET6 | |||||
if (dst->sa_family == AF_INET6) { | |||||
struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst; | |||||
if (IN6_IS_SCOPE_LINKLOCAL(&dst6->sin6_addr)) | |||||
in6_set_unicast_scopeid(&dst6->sin6_addr, ifp->if_index); | |||||
} | |||||
#endif | |||||
struct llentry *lle = lla_lookup(llt, LLE_UNLOCKED, dst); | |||||
if (lle == NULL) | |||||
return (ESRCH); | |||||
wa->ifp = ifp; | |||||
wa->family = family; | |||||
return (dump_lle(llt, lle, wa)); | |||||
} | |||||
struct nl_parsed_neigh { | |||||
struct sockaddr *nda_dst; | |||||
struct ifnet *nda_ifp; | |||||
struct nlattr *nda_lladdr; | |||||
uint32_t ndm_flags; | |||||
uint16_t ndm_state; | |||||
uint8_t ndm_family; | |||||
}; | |||||
#define _OFF_S(_field) offsetof(struct nl_parsed_neigh, _field) | |||||
static struct nlattr_parser ps[] = { | |||||
{ .type = NDA_DST, .off = _OFF_S(nda_dst), .cb = nlattr_get_ip }, | |||||
{ .type = NDA_LLADDR, .off = _OFF_S(nda_lladdr), .cb = nlattr_get_nla }, | |||||
{ .type = NDA_IFINDEX, .off = _OFF_S(nda_ifp), .cb = nlattr_get_ifp }, | |||||
{ .type = NDA_FLAGS_EXT, .off = _OFF_S(ndm_flags), .cb = nlattr_get_uint32 }, | |||||
}; | |||||
#undef _OFF_S | |||||
static int | |||||
nlf_get_u8_u32(void *src, struct netlink_parse_tracker *npt, void *target) | |||||
{ | |||||
*((uint32_t *)target) = *((const uint8_t *)src); | |||||
return (0); | |||||
} | |||||
static int | |||||
nlf_get_u16(void *src, struct netlink_parse_tracker *npt, void *target) | |||||
{ | |||||
*((uint16_t *)target) = *((const uint16_t *)src); | |||||
return (0); | |||||
} | |||||
#define _OFF_IN(_field) offsetof(struct ndmsg, _field) | |||||
#define _OFF_OUT(_field) offsetof(struct nl_parsed_neigh, _field) | |||||
static struct nlfield_parser fpneigh[] = { | |||||
{ .off_in = _OFF_IN(ndm_family), .off_out = _OFF_OUT(ndm_family), .cb = nlf_get_u8 }, | |||||
{ .off_in = _OFF_IN(ndm_flags), .off_out = _OFF_OUT(ndm_flags), .cb = nlf_get_u8_u32 }, | |||||
{ .off_in = _OFF_IN(ndm_state), .off_out = _OFF_OUT(ndm_state), .cb = nlf_get_u16 }, | |||||
{ .off_in = _OFF_IN(ndm_ifindex), .off_out = _OFF_OUT(nda_ifp), .cb = nlf_get_ifpz }, | |||||
}; | |||||
#undef _OFF_IN | |||||
#undef _OFF_OUT | |||||
static struct nlhdr_parser ndmsg_parser = { | |||||
.hdr_off = sizeof(struct ndmsg), | |||||
.fp_size = sizeof(fpneigh) / sizeof(fpneigh[0]), | |||||
.np_size = sizeof(ps) / sizeof(ps[0]), | |||||
.fp = fpneigh, | |||||
.np = ps, | |||||
}; | |||||
/* | |||||
* type=RTM_NEWNEIGH, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1661941473, pid=0}, | |||||
* {ndm_family=AF_INET6, ndm_ifindex=if_nametoindex("enp0s31f6"), ndm_state=NUD_PERMANENT, ndm_flags=0, ndm_type=RTN_UNSPEC}, | |||||
* [ | |||||
* {{nla_len=20, nla_type=NDA_DST}, inet_pton(AF_INET6, "2a01:4f8:13a:70c::3")}, | |||||
* {{nla_len=10, nla_type=NDA_LLADDR}, 20:4e:71:62:ae:f2}]}, iov_len=60} | |||||
*/ | |||||
static int | |||||
rtnl_handle_newneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct netlink_parse_tracker *npt) | |||||
{ | |||||
int error; | |||||
error = nlp_has_priv_route(nlp); | |||||
if (error != 0) | |||||
return (error); | |||||
struct nl_parsed_neigh attrs = {}; | |||||
error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs); | |||||
if (error != 0) | |||||
return (error); | |||||
if (attrs.nda_ifp == NULL || attrs.nda_dst == NULL || attrs.nda_lladdr == NULL) { | |||||
if (attrs.nda_ifp == NULL) | |||||
NLMSG_REPORT_ERR_MSG(npt, "NDA_IFINDEX / ndm_ifindex not set"); | |||||
if (attrs.nda_dst == NULL) | |||||
NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set"); | |||||
if (attrs.nda_lladdr == NULL) | |||||
NLMSG_REPORT_ERR_MSG(npt, "NDA_LLADDR not set"); | |||||
return (EINVAL); | |||||
} | |||||
if (attrs.nda_dst->sa_family != attrs.ndm_family) { | |||||
NLMSG_REPORT_ERR_MSG(npt, | |||||
"NDA_DST family (%d) is different from ndm_family (%d)", | |||||
attrs.nda_dst->sa_family, attrs.ndm_family); | |||||
return (EINVAL); | |||||
} | |||||
int addrlen = attrs.nda_ifp->if_addrlen; | |||||
if (attrs.nda_lladdr->nla_len != sizeof(struct nlattr) + addrlen) { | |||||
NLMSG_REPORT_ERR_MSG(npt, | |||||
"NDA_LLADDR address length (%ld) is different from expected (%d)", | |||||
attrs.nda_lladdr->nla_len - sizeof(struct nlattr), addrlen); | |||||
return (EINVAL); | |||||
} | |||||
if (attrs.ndm_state != NUD_PERMANENT) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "ndm_state %d not supported", attrs.ndm_state); | |||||
return (ENOTSUP); | |||||
} | |||||
const uint16_t supported_flags = NTF_PROXY | NTF_STICKY; | |||||
if ((attrs.ndm_flags & supported_flags) != attrs.ndm_flags) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "ndm_flags %X not supported", | |||||
attrs.ndm_flags &~ supported_flags); | |||||
return (ENOTSUP); | |||||
} | |||||
/* Replacement requires new entry creation anyway */ | |||||
if ((hdr->nlmsg_flags & (NLM_F_CREATE | NLM_F_REPLACE)) == 0) | |||||
return (ENOTSUP); | |||||
struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family); | |||||
if (llt == NULL) | |||||
return (EAFNOSUPPORT); | |||||
uint8_t linkhdr[LLE_MAX_LINKHDR]; | |||||
size_t linkhdrsize = sizeof(linkhdr); | |||||
int lladdr_off = 0; | |||||
if (lltable_calc_llheader(attrs.nda_ifp, attrs.ndm_family, | |||||
(char *)(attrs.nda_lladdr + 1), linkhdr, &linkhdrsize, &lladdr_off) != 0) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "unable to calculate lle prepend data"); | |||||
return (EINVAL); | |||||
} | |||||
int lle_flags = LLE_STATIC | ((attrs.ndm_flags & NTF_PROXY) ? LLE_PUB : 0); | |||||
struct llentry *lle = lltable_alloc_entry(llt, lle_flags, attrs.nda_dst); | |||||
if (lle == NULL) | |||||
return (ENOMEM); | |||||
lltable_set_entry_addr(attrs.nda_ifp, lle, linkhdr, linkhdrsize, lladdr_off); | |||||
/* llentry created, try to insert or update :*/ | |||||
IF_AFDATA_WLOCK(attrs.nda_ifp); | |||||
LLE_WLOCK(lle); | |||||
struct llentry *lle_tmp = lla_lookup(llt, LLE_EXCLUSIVE, attrs.nda_dst); | |||||
if (lle_tmp != NULL) { | |||||
if (hdr->nlmsg_flags & NLM_F_EXCL) { | |||||
LLE_WUNLOCK(lle_tmp); | |||||
lle_tmp = NULL; | |||||
error = EEXIST; | |||||
} else if (hdr->nlmsg_flags & NLM_F_REPLACE) { | |||||
lltable_unlink_entry(llt, lle_tmp); | |||||
lltable_link_entry(llt, lle); | |||||
} else | |||||
error = EEXIST; | |||||
} else { | |||||
if (hdr->nlmsg_flags & NLM_F_CREATE) | |||||
lltable_link_entry(llt, lle); | |||||
else | |||||
error = ENOENT; | |||||
} | |||||
IF_AFDATA_WUNLOCK(attrs.nda_ifp); | |||||
if (error != 0) { | |||||
if (lle != NULL) | |||||
llentry_free(lle); | |||||
return (error); | |||||
} | |||||
if (lle_tmp != NULL) | |||||
llentry_free(lle_tmp); | |||||
/* XXX: We're inside epoch */ | |||||
EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_RESOLVED); | |||||
LLE_WUNLOCK(lle); | |||||
return (0); | |||||
} | |||||
static int | |||||
rtnl_handle_delneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct netlink_parse_tracker *npt) | |||||
{ | |||||
int error; | |||||
error = nlp_has_priv_route(nlp); | |||||
if (error != 0) | |||||
return (error); | |||||
struct nl_parsed_neigh attrs = {}; | |||||
error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs); | |||||
if (error != 0) | |||||
return (error); | |||||
if (attrs.nda_dst == NULL) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set"); | |||||
return (EINVAL); | |||||
} | |||||
if (attrs.nda_ifp == NULL) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "no ifindex provided"); | |||||
return (EINVAL); | |||||
} | |||||
struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family); | |||||
if (llt == NULL) | |||||
return (EAFNOSUPPORT); | |||||
IF_AFDATA_WLOCK(attrs.nda_ifp); | |||||
struct llentry *lle = lla_lookup(llt, LLE_EXCLUSIVE, attrs.nda_dst); | |||||
if (lle != NULL) { | |||||
if ((lle->la_flags & LLE_IFADDR) != 0) { | |||||
LLE_WUNLOCK(lle); | |||||
lle = NULL; | |||||
error = EPERM; | |||||
} else | |||||
lltable_unlink_entry(llt, lle); | |||||
} else | |||||
error = ENOENT; | |||||
IF_AFDATA_WUNLOCK(attrs.nda_ifp); | |||||
if (error == 0 && lle != NULL) | |||||
EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_DELETED); | |||||
if (lle != NULL) | |||||
llentry_free(lle); | |||||
return (error); | |||||
} | |||||
static int | |||||
rtnl_handle_getneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct netlink_parse_tracker *npt) | |||||
{ | |||||
int error; | |||||
struct nl_parsed_neigh attrs = {}; | |||||
error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs); | |||||
if (error != 0) | |||||
return (error); | |||||
if (attrs.nda_dst != NULL && attrs.nda_ifp == NULL) { | |||||
NLMSG_REPORT_ERR_MSG(npt, "has NDA_DST but no ifindex provided"); | |||||
return (EINVAL); | |||||
} | |||||
struct netlink_walkargs wa = { | |||||
.so = nlp, | |||||
.ns = npt->ns, | |||||
.hdr.nlmsg_pid = hdr->nlmsg_pid, | |||||
.hdr.nlmsg_seq = hdr->nlmsg_seq, | |||||
.hdr.nlmsg_flags = hdr->nlmsg_flags, | |||||
.hdr.nlmsg_type = NL_RTM_NEWNEIGH, | |||||
}; | |||||
if (attrs.nda_dst == NULL) | |||||
error = dump_llts(&wa, attrs.nda_ifp, attrs.ndm_family); | |||||
else | |||||
error = get_lle(&wa, attrs.nda_ifp, attrs.ndm_family, attrs.nda_dst); | |||||
return (error); | |||||
} | |||||
static struct rtnl_cmd_handler cmd_handlers[] = { | |||||
{ NL_RTM_NEWNEIGH, "RTM_NEWNEIGH", &rtnl_handle_newneigh, sizeof(struct ndmsg)}, | |||||
{ NL_RTM_DELNEIGH, "RTM_DELNEIGH", &rtnl_handle_delneigh, sizeof(struct ndmsg)}, | |||||
{ NL_RTM_GETNEIGH, "RTM_GETNEIGH", &rtnl_handle_getneigh, sizeof(struct ndmsg)}, | |||||
}; | |||||
static void | |||||
rtnl_lle_event(void *arg __unused, struct llentry *lle, int evt) | |||||
{ | |||||
struct ifnet *ifp; | |||||
int family; | |||||
LLE_WLOCK_ASSERT(lle); | |||||
ifp = lltable_get_ifp(lle->lle_tbl); | |||||
family = lltable_get_af(lle->lle_tbl); | |||||
if (family != AF_INET && family != AF_INET6) | |||||
return; | |||||
int nlmsgs_type = evt == LLENTRY_RESOLVED ? NL_RTM_NEWNEIGH : NL_RTM_DELNEIGH; | |||||
struct nlmsg_state ns = {}; | |||||
if (!nlmsg_get_group_writer(NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEIGH, &ns)) { | |||||
NL_LOG(LOG_DEBUG, "error allocating group writer"); | |||||
return; | |||||
} | |||||
struct netlink_walkargs wa = { | |||||
.hdr.nlmsg_type = nlmsgs_type, | |||||
.ns = &ns, | |||||
.ifp = ifp, | |||||
.family = family, | |||||
}; | |||||
dump_lle_locked(lle->lle_tbl, lle, &wa); | |||||
nlmsg_flush(&ns); | |||||
} | |||||
void | |||||
rtnl_neighs_init() | |||||
{ | |||||
rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers)); | |||||
lle_event_p = EVENTHANDLER_REGISTER(lle_event, rtnl_lle_event, NULL, | |||||
EVENTHANDLER_PRI_ANY); | |||||
} |