summaryrefslogtreecommitdiffstats
path: root/sys/netinet6/nd6.c
diff options
context:
space:
mode:
authoritojun <itojun@FreeBSD.org>2000-07-04 16:35:15 +0000
committeritojun <itojun@FreeBSD.org>2000-07-04 16:35:15 +0000
commit5f4e854de19331a53788d6100bbcd42845056bc1 (patch)
tree3ff8c876a5868b103fb8713055d83e29a3fa38d5 /sys/netinet6/nd6.c
parentbdc16885232d771a99d7dfc247cd27a44cd061f9 (diff)
downloadFreeBSD-src-5f4e854de19331a53788d6100bbcd42845056bc1.zip
FreeBSD-src-5f4e854de19331a53788d6100bbcd42845056bc1.tar.gz
sync with kame tree as of july00. tons of bug fixes/improvements.
API changes: - additional IPv6 ioctls - IPsec PF_KEY API was changed, it is mandatory to upgrade setkey(8). (also syntax change)
Diffstat (limited to 'sys/netinet6/nd6.c')
-rw-r--r--sys/netinet6/nd6.c700
1 files changed, 579 insertions, 121 deletions
diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c
index 31e975c..0a56a91 100644
--- a/sys/netinet6/nd6.c
+++ b/sys/netinet6/nd6.c
@@ -1,3 +1,6 @@
+/* $FreeBSD$ */
+/* $KAME: nd6.c,v 1.68 2000/07/02 14:48:02 itojun Exp $ */
+
/*
* Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
@@ -25,8 +28,6 @@
* 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.
- *
- * $FreeBSD$
*/
/*
@@ -37,6 +38,9 @@
* I left the code mostly as it was in 970310. -- itojun
*/
+#include "opt_inet.h"
+#include "opt_inet6.h"
+
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
@@ -45,6 +49,7 @@
#include <sys/sockio.h>
#include <sys/time.h>
#include <sys/kernel.h>
+#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/queue.h>
@@ -59,21 +64,21 @@
#include <netinet/if_ether.h>
#include <netinet/if_fddi.h>
#include <netinet6/in6_var.h>
-#include <netinet6/ip6.h>
+#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/nd6.h>
#include <netinet6/in6_prefix.h>
-#include <netinet6/icmp6.h>
+#include <netinet/icmp6.h>
#include "loop.h"
#include <net/net_osdep.h>
-#define ND6_SLOWTIMER_INTERVAL (60 * 60) /* 1 hour */
-#define ND6_RECALC_REACHTM_INTERVAL (60 * 120) /* 2 hours */
+#define ND6_SLOWTIMER_INTERVAL (60 * 60) /* 1 hour */
+#define ND6_RECALC_REACHTM_INTERVAL (60 * 120) /* 2 hours */
-#define SIN6(s) ((struct sockaddr_in6 *)s)
-#define SDL(s) ((struct sockaddr_dl *)s)
+#define SIN6(s) ((struct sockaddr_in6 *)s)
+#define SDL(s) ((struct sockaddr_dl *)s)
/* timer values */
int nd6_prune = 1; /* walk list every 1 seconds */
@@ -81,20 +86,25 @@ int nd6_delay = 5; /* delay first probe time 5 second */
int nd6_umaxtries = 3; /* maximum unicast query */
int nd6_mmaxtries = 3; /* maximum multicast query */
int nd6_useloopback = 1; /* use loopback interface for local traffic */
-int nd6_proxyall = 0; /* enable Proxy Neighbor Advertisement */
+
+/* preventing too many loops in ND option parsing */
+int nd6_maxndopt = 10; /* max # of ND options allowed */
+
+int nd6_maxnudhint = 0; /* max # of subsequent upper layer hints */
/* for debugging? */
-static int nd6_inuse, nd6_allocated;
+static int nd6_inuse, nd6_allocated;
-struct llinfo_nd6 llinfo_nd6 = {&llinfo_nd6, &llinfo_nd6};
-struct nd_ifinfo *nd_ifinfo = NULL;
-struct nd_drhead nd_defrouter = { 0 };
-struct nd_prhead nd_prefix = { 0 };
+struct llinfo_nd6 llinfo_nd6 = {&llinfo_nd6, &llinfo_nd6};
+static size_t nd_ifinfo_indexlim = 8;
+struct nd_ifinfo *nd_ifinfo = NULL;
+struct nd_drhead nd_defrouter;
+struct nd_prhead nd_prefix = { 0 };
-int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL;
-static struct sockaddr_in6 all1_sa;
+int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL;
+static struct sockaddr_in6 all1_sa;
-static void nd6_slowtimo __P((void *));
+static void nd6_slowtimo __P((void *));
void
nd6_init()
@@ -112,6 +122,9 @@ nd6_init()
for (i = 0; i < sizeof(all1_sa.sin6_addr); i++)
all1_sa.sin6_addr.s6_addr[i] = 0xff;
+ /* initialization of the default router list */
+ TAILQ_INIT(&nd_defrouter);
+
nd6_init_done = 1;
/* start timer */
@@ -122,21 +135,20 @@ void
nd6_ifattach(ifp)
struct ifnet *ifp;
{
- static size_t if_indexlim = 8;
/*
* We have some arrays that should be indexed by if_index.
* since if_index will grow dynamically, they should grow too.
*/
- if (nd_ifinfo == NULL || if_index >= if_indexlim) {
+ if (nd_ifinfo == NULL || if_index >= nd_ifinfo_indexlim) {
size_t n;
caddr_t q;
- while (if_index >= if_indexlim)
- if_indexlim <<= 1;
+ while (if_index >= nd_ifinfo_indexlim)
+ nd_ifinfo_indexlim <<= 1;
/* grow nd_ifinfo */
- n = if_indexlim * sizeof(struct nd_ifinfo);
+ n = nd_ifinfo_indexlim * sizeof(struct nd_ifinfo);
q = (caddr_t)malloc(n, M_IP6NDP, M_WAITOK);
bzero(q, n);
if (nd_ifinfo) {
@@ -147,12 +159,18 @@ nd6_ifattach(ifp)
}
#define ND nd_ifinfo[ifp->if_index]
+
+ /* don't initialize if called twice */
+ if (ND.linkmtu)
+ return;
+
ND.linkmtu = ifindex2ifnet[ifp->if_index]->if_mtu;
ND.chlim = IPV6_DEFHLIM;
ND.basereachable = REACHABLE_TIME;
ND.reachable = ND_COMPUTE_RTIME(ND.basereachable);
ND.retrans = RETRANS_TIMER;
ND.receivedra = 0;
+ ND.flags = ND6_IFF_PERFORMNUD;
nd6_setmtu(ifp);
#undef ND
}
@@ -330,14 +348,15 @@ nd6_options(ndopts)
* Unknown options must be silently ignored,
* to accomodate future extension to the protocol.
*/
- log(LOG_INFO,
+ log(LOG_DEBUG,
"nd6_options: unsupported option %d - "
"option ignored\n", nd_opt->nd_opt_type);
}
skip1:
i++;
- if (i > 10) {
+ if (i > nd6_maxndopt) {
+ icmp6stat.icp6s_nd_toomanyopt++;
printf("too many loop in nd opt\n");
break;
}
@@ -371,6 +390,8 @@ nd6_timer(ignored_arg)
struct ifnet *ifp;
struct sockaddr_in6 *dst;
struct llinfo_nd6 *next = ln->ln_next;
+ /* XXX: used for the DELAY case only: */
+ struct nd_ifinfo *ndi = NULL;
if ((rt = ln->ln_rt) == NULL) {
ln = next;
@@ -380,6 +401,7 @@ nd6_timer(ignored_arg)
ln = next;
continue;
}
+ ndi = &nd_ifinfo[ifp->if_index];
dst = (struct sockaddr_in6 *)rt_key(rt);
if (ln->ln_expire > time_second) {
@@ -390,6 +412,9 @@ nd6_timer(ignored_arg)
/* sanity check */
if (!rt)
panic("rt=0 in nd6_timer(ln=%p)\n", ln);
+ if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln)
+ panic("rt_llinfo(%p) is not equal to ln(%p)\n",
+ rt->rt_llinfo, ln);
if (!dst)
panic("dst=0 in nd6_timer(ln=%p)\n", ln);
@@ -422,23 +447,26 @@ nd6_timer(ignored_arg)
}
break;
case ND6_LLINFO_REACHABLE:
- if (ln->ln_expire) {
+ if (ln->ln_expire)
ln->ln_state = ND6_LLINFO_STALE;
- }
break;
/*
* ND6_LLINFO_STALE state requires nothing for timer
* routine.
*/
case ND6_LLINFO_DELAY:
- ln->ln_asked = 1;
- ln->ln_state = ND6_LLINFO_PROBE;
- ln->ln_expire = time_second +
- nd_ifinfo[ifp->if_index].retrans / 1000;
- nd6_ns_output(ifp, &dst->sin6_addr, &dst->sin6_addr,
- ln, 0);
+ if (ndi && (ndi->flags & ND6_IFF_PERFORMNUD) != 0) {
+ /* We need NUD */
+ ln->ln_asked = 1;
+ ln->ln_state = ND6_LLINFO_PROBE;
+ ln->ln_expire = time_second +
+ ndi->retrans / 1000;
+ nd6_ns_output(ifp, &dst->sin6_addr,
+ &dst->sin6_addr,
+ ln, 0);
+ } else
+ ln->ln_state = ND6_LLINFO_STALE; /* XXX */
break;
-
case ND6_LLINFO_PROBE:
if (ln->ln_asked < nd6_umaxtries) {
ln->ln_asked++;
@@ -458,17 +486,18 @@ nd6_timer(ignored_arg)
}
/* expire */
- dr = LIST_FIRST(&nd_defrouter);
+ dr = TAILQ_FIRST(&nd_defrouter);
while (dr) {
if (dr->expire && dr->expire < time_second) {
struct nd_defrouter *t;
- t = LIST_NEXT(dr, dr_entry);
+ t = TAILQ_NEXT(dr, dr_entry);
defrtrlist_del(dr);
dr = t;
- } else
- dr = LIST_NEXT(dr, dr_entry);
+ } else {
+ dr = TAILQ_NEXT(dr, dr_entry);
+ }
}
- pr = LIST_FIRST(&nd_prefix);
+ pr = nd_prefix.lh_first;
while (pr) {
struct in6_ifaddr *ia6;
struct in6_addrlifetime *lt6;
@@ -503,7 +532,7 @@ nd6_timer(ignored_arg)
if (pr->ndpr_expire
&& pr->ndpr_expire + NDPR_KEEP_EXPIRED < time_second) {
struct nd_prefix *t;
- t = LIST_NEXT(pr, ndpr_entry);
+ t = pr->ndpr_next;
/*
* address expiration and prefix expiration are
@@ -513,11 +542,104 @@ nd6_timer(ignored_arg)
prelist_remove(pr);
pr = t;
} else
- pr = LIST_NEXT(pr, ndpr_entry);
+ pr = pr->ndpr_next;
}
splx(s);
}
+/*
+ * Nuke neighbor cache/prefix/default router management table, right before
+ * ifp goes away.
+ */
+void
+nd6_purge(ifp)
+ struct ifnet *ifp;
+{
+ struct llinfo_nd6 *ln, *nln;
+ struct nd_defrouter *dr, *ndr, drany;
+ struct nd_prefix *pr, *npr;
+
+ /* Nuke default router list entries toward ifp */
+ if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
+ /*
+ * The first entry of the list may be stored in
+ * the routing table, so we'll delete it later.
+ */
+ for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = ndr) {
+ ndr = TAILQ_NEXT(dr, dr_entry);
+ if (dr->ifp == ifp)
+ defrtrlist_del(dr);
+ }
+ dr = TAILQ_FIRST(&nd_defrouter);
+ if (dr->ifp == ifp)
+ defrtrlist_del(dr);
+ }
+
+ /* Nuke prefix list entries toward ifp */
+ for (pr = nd_prefix.lh_first; pr; pr = npr) {
+ npr = pr->ndpr_next;
+ if (pr->ndpr_ifp == ifp) {
+ if (!IN6_IS_ADDR_UNSPECIFIED(&pr->ndpr_addr))
+ in6_ifdel(pr->ndpr_ifp, &pr->ndpr_addr);
+ prelist_remove(pr);
+ }
+ }
+
+ /* cancel default outgoing interface setting */
+ if (nd6_defifindex == ifp->if_index)
+ nd6_setdefaultiface(0);
+
+ /* refresh default router list */
+ bzero(&drany, sizeof(drany));
+ defrouter_delreq(&drany, 0);
+ defrouter_select();
+
+ /*
+ * Nuke neighbor cache entries for the ifp.
+ * Note that rt->rt_ifp may not be the same as ifp,
+ * due to KAME goto ours hack. See RTM_RESOLVE case in
+ * nd6_rtrequest(), and ip6_input().
+ */
+ ln = llinfo_nd6.ln_next;
+ while (ln && ln != &llinfo_nd6) {
+ struct rtentry *rt;
+ struct sockaddr_dl *sdl;
+
+ nln = ln->ln_next;
+ rt = ln->ln_rt;
+ if (rt && rt->rt_gateway &&
+ rt->rt_gateway->sa_family == AF_LINK) {
+ sdl = (struct sockaddr_dl *)rt->rt_gateway;
+ if (sdl->sdl_index == ifp->if_index)
+ nd6_free(rt);
+ }
+ ln = nln;
+ }
+
+ /*
+ * Neighbor cache entry for interface route will be retained
+ * with ND6_LLINFO_WAITDELETE state, by nd6_free(). Nuke it.
+ */
+ ln = llinfo_nd6.ln_next;
+ while (ln && ln != &llinfo_nd6) {
+ struct rtentry *rt;
+ struct sockaddr_dl *sdl;
+
+ nln = ln->ln_next;
+ rt = ln->ln_rt;
+ if (rt && rt->rt_gateway &&
+ rt->rt_gateway->sa_family == AF_LINK) {
+ sdl = (struct sockaddr_dl *)rt->rt_gateway;
+ if (sdl->sdl_index == ifp->if_index) {
+ rtrequest(RTM_DELETE, rt_key(rt),
+ (struct sockaddr *)0, rt_mask(rt), 0,
+ (struct rtentry **)0);
+ }
+ }
+ ln = nln;
+ }
+}
+
struct rtentry *
nd6_lookup(addr6, create, ifp)
struct in6_addr *addr6;
@@ -531,6 +653,9 @@ nd6_lookup(addr6, create, ifp)
sin6.sin6_len = sizeof(struct sockaddr_in6);
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = *addr6;
+#ifdef SCOPEDROUTING
+ sin6.sin6_scope_id = in6_addr2scopeid(ifp, addr6);
+#endif
rt = rtalloc1((struct sockaddr *)&sin6, create, 0UL);
if (rt && (rt->rt_flags & RTF_LLINFO) == 0) {
/*
@@ -546,6 +671,8 @@ nd6_lookup(addr6, create, ifp)
}
if (!rt) {
if (create && ifp) {
+ int e;
+
/*
* If no route is available and create is set,
* we allocate a host route for the destination
@@ -564,15 +691,17 @@ nd6_lookup(addr6, create, ifp)
* destination in nd6_rtrequest which will be
* called in rtequest via ifa->ifa_rtrequest.
*/
- if (rtrequest(RTM_ADD, (struct sockaddr *)&sin6,
- ifa->ifa_addr,
- (struct sockaddr *)&all1_sa,
- (ifa->ifa_flags |
- RTF_HOST | RTF_LLINFO) & ~RTF_CLONING,
- &rt))
+ if ((e = rtrequest(RTM_ADD, (struct sockaddr *)&sin6,
+ ifa->ifa_addr,
+ (struct sockaddr *)&all1_sa,
+ (ifa->ifa_flags |
+ RTF_HOST | RTF_LLINFO) &
+ ~RTF_CLONING,
+ &rt)) != 0)
log(LOG_ERR,
"nd6_lookup: failed to add route for a "
- "neighbor(%s)\n", ip6_sprintf(addr6));
+ "neighbor(%s), errno=%d\n",
+ ip6_sprintf(addr6), e);
if (rt == NULL)
return(NULL);
if (rt->rt_llinfo) {
@@ -610,7 +739,7 @@ nd6_lookup(addr6, create, ifp)
*/
int
nd6_is_addr_neighbor(addr, ifp)
- struct in6_addr *addr;
+ struct sockaddr_in6 *addr;
struct ifnet *ifp;
{
register struct ifaddr *ifa;
@@ -619,21 +748,29 @@ nd6_is_addr_neighbor(addr, ifp)
#define IFADDR6(a) ((((struct in6_ifaddr *)(a))->ia_addr).sin6_addr)
#define IFMASK6(a) ((((struct in6_ifaddr *)(a))->ia_prefixmask).sin6_addr)
- /* A link-local address is always a neighbor. */
- if (IN6_IS_ADDR_LINKLOCAL(addr))
+ /*
+ * A link-local address is always a neighbor.
+ * XXX: we should use the sin6_scope_id field rather than the embedded
+ * interface index.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) &&
+ ntohs(*(u_int16_t *)&addr->sin6_addr.s6_addr[2]) == ifp->if_index)
return(1);
/*
* If the address matches one of our addresses,
* it should be a neighbor.
*/
- TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list)
+ for (ifa = ifp->if_addrlist.tqh_first;
+ ifa;
+ ifa = ifa->ifa_list.tqe_next)
{
if (ifa->ifa_addr->sa_family != AF_INET6)
next: continue;
for (i = 0; i < 4; i++) {
- if ((IFADDR6(ifa).s6_addr32[i] ^ addr->s6_addr32[i]) &
+ if ((IFADDR6(ifa).s6_addr32[i] ^
+ addr->sin6_addr.s6_addr32[i]) &
IFMASK6(ifa).s6_addr32[i])
goto next;
}
@@ -644,7 +781,7 @@ nd6_is_addr_neighbor(addr, ifp)
* Even if the address matches none of our addresses, it might be
* in the neighbor cache.
*/
- if (nd6_lookup(addr, 0, ifp))
+ if (nd6_lookup(&addr->sin6_addr, 0, ifp))
return(1);
return(0);
@@ -661,40 +798,74 @@ nd6_free(rt)
{
struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo;
struct sockaddr_dl *sdl;
+ struct in6_addr in6 = ((struct sockaddr_in6 *)rt_key(rt))->sin6_addr;
+ struct nd_defrouter *dr;
- if (ln->ln_router) {
- /* remove from default router list */
- struct nd_defrouter *dr;
- struct in6_addr *in6;
- int s;
- in6 = &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr;
+ /*
+ * Clear all destination cache entries for the neighbor.
+ * XXX: is it better to restrict this to hosts?
+ */
+ pfctlinput(PRC_HOSTDEAD, rt_key(rt));
+ if (!ip6_forwarding && ip6_accept_rtadv) { /* XXX: too restrictive? */
+ int s;
s = splnet();
- dr = defrouter_lookup(&((struct sockaddr_in6 *)rt_key(rt))->
- sin6_addr,
+ dr = defrouter_lookup(&((struct sockaddr_in6 *)rt_key(rt))->sin6_addr,
rt->rt_ifp);
- if (dr)
- defrtrlist_del(dr);
- else if (!ip6_forwarding && ip6_accept_rtadv) {
+ if (ln->ln_router || dr) {
+ /*
+ * rt6_flush must be called whether or not the neighbor
+ * is in the Default Router List.
+ * See a corresponding comment in nd6_na_input().
+ */
+ rt6_flush(&in6, rt->rt_ifp);
+ }
+
+ if (dr) {
/*
- * rt6_flush must be called in any case.
- * see the comment in nd6_na_input().
+ * Unreachablity of a router might affect the default
+ * router selection and on-link detection of advertised
+ * prefixes.
*/
- rt6_flush(in6, rt->rt_ifp);
+
+ /*
+ * Temporarily fake the state to choose a new default
+ * router and to perform on-link determination of
+ * prefixes coreectly.
+ * Below the state will be set correctly,
+ * or the entry itself will be deleted.
+ */
+ ln->ln_state = ND6_LLINFO_INCOMPLETE;
+
+ if (dr == TAILQ_FIRST(&nd_defrouter)) {
+ /*
+ * It is used as the current default router,
+ * so we have to move it to the end of the
+ * list and choose a new one.
+ * XXX: it is not very efficient if this is
+ * the only router.
+ */
+ TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
+ TAILQ_INSERT_TAIL(&nd_defrouter, dr, dr_entry);
+
+ defrouter_select();
+ }
+ pfxlist_onlink_check();
}
splx(s);
}
-
+
if (rt->rt_refcnt > 0 && (sdl = SDL(rt->rt_gateway)) &&
- sdl->sdl_family == AF_LINK) {
+ sdl->sdl_family == AF_LINK) {
sdl->sdl_alen = 0;
ln->ln_state = ND6_LLINFO_WAITDELETE;
ln->ln_asked = 0;
rt->rt_flags &= ~RTF_REJECT;
return;
}
- rtrequest(RTM_DELETE, rt_key(rt), (struct sockaddr *)0, rt_mask(rt),
- 0, (struct rtentry **)0);
+
+ rtrequest(RTM_DELETE, rt_key(rt), (struct sockaddr *)0,
+ rt_mask(rt), 0, (struct rtentry **)0);
}
/*
@@ -703,9 +874,10 @@ nd6_free(rt)
* XXX cost-effective metods?
*/
void
-nd6_nud_hint(rt, dst6)
+nd6_nud_hint(rt, dst6, force)
struct rtentry *rt;
struct in6_addr *dst6;
+ int force;
{
struct llinfo_nd6 *ln;
@@ -720,11 +892,10 @@ nd6_nud_hint(rt, dst6)
return;
}
- if ((rt->rt_flags & RTF_GATEWAY)
- || (rt->rt_flags & RTF_LLINFO) == 0
- || !rt->rt_llinfo
- || !rt->rt_gateway
- || rt->rt_gateway->sa_family != AF_LINK) {
+ if ((rt->rt_flags & RTF_GATEWAY) != 0 ||
+ (rt->rt_flags & RTF_LLINFO) == 0 ||
+ !rt->rt_llinfo || !rt->rt_gateway ||
+ rt->rt_gateway->sa_family != AF_LINK) {
/* This is not a host route. */
return;
}
@@ -733,12 +904,117 @@ nd6_nud_hint(rt, dst6)
if (ln->ln_state < ND6_LLINFO_REACHABLE)
return;
+ /*
+ * if we get upper-layer reachability confirmation many times,
+ * it is possible we have false information.
+ */
+ if (!force) {
+ ln->ln_byhint++;
+ if (ln->ln_byhint > nd6_maxnudhint)
+ return;
+ }
+
ln->ln_state = ND6_LLINFO_REACHABLE;
if (ln->ln_expire)
ln->ln_expire = time_second +
nd_ifinfo[rt->rt_ifp->if_index].reachable;
}
+#ifdef OLDIP6OUTPUT
+/*
+ * Resolve an IP6 address into an ethernet address. If success,
+ * desten is filled in. If there is no entry in ndptab,
+ * set one up and multicast a solicitation for the IP6 address.
+ * Hold onto this mbuf and resend it once the address
+ * is finally resolved. A return value of 1 indicates
+ * that desten has been filled in and the packet should be sent
+ * normally; a 0 return indicates that the packet has been
+ * taken over here, either now or for later transmission.
+ */
+int
+nd6_resolve(ifp, rt, m, dst, desten)
+ struct ifnet *ifp;
+ struct rtentry *rt;
+ struct mbuf *m;
+ struct sockaddr *dst;
+ u_char *desten;
+{
+ struct llinfo_nd6 *ln = (struct llinfo_nd6 *)NULL;
+ struct sockaddr_dl *sdl;
+
+ if (m->m_flags & M_MCAST) {
+ switch (ifp->if_type) {
+ case IFT_ETHER:
+ case IFT_FDDI:
+ ETHER_MAP_IPV6_MULTICAST(&SIN6(dst)->sin6_addr,
+ desten);
+ return(1);
+ break;
+ case IFT_ARCNET:
+ *desten = 0;
+ return(1);
+ break;
+ default:
+ return(0);
+ }
+ }
+ if (rt && (rt->rt_flags & RTF_LLINFO) != 0)
+ ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+ else {
+ if ((rt = nd6_lookup(&(SIN6(dst)->sin6_addr), 1, ifp)) != NULL)
+ ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+ }
+ if (!ln || !rt) {
+ log(LOG_DEBUG, "nd6_resolve: can't allocate llinfo for %s\n",
+ ip6_sprintf(&(SIN6(dst)->sin6_addr)));
+ m_freem(m);
+ return(0);
+ }
+ sdl = SDL(rt->rt_gateway);
+ /*
+ * Ckeck the address family and length is valid, the address
+ * is resolved; otherwise, try to resolve.
+ */
+ if (ln->ln_state >= ND6_LLINFO_REACHABLE
+ && sdl->sdl_family == AF_LINK
+ && sdl->sdl_alen != 0) {
+ bcopy(LLADDR(sdl), desten, sdl->sdl_alen);
+ if (ln->ln_state == ND6_LLINFO_STALE) {
+ ln->ln_asked = 0;
+ ln->ln_state = ND6_LLINFO_DELAY;
+ ln->ln_expire = time_second + nd6_delay;
+ }
+ return(1);
+ }
+ /*
+ * There is an ndp entry, but no ethernet address
+ * response yet. Replace the held mbuf with this
+ * latest one.
+ *
+ * XXX Does the code conform to rate-limiting rule?
+ * (RFC 2461 7.2.2)
+ */
+ if (ln->ln_state == ND6_LLINFO_WAITDELETE ||
+ ln->ln_state == ND6_LLINFO_NOSTATE)
+ ln->ln_state = ND6_LLINFO_INCOMPLETE;
+ if (ln->ln_hold)
+ m_freem(ln->ln_hold);
+ ln->ln_hold = m;
+ if (ln->ln_expire) {
+ rt->rt_flags &= ~RTF_REJECT;
+ if (ln->ln_asked < nd6_mmaxtries &&
+ ln->ln_expire < time_second) {
+ ln->ln_asked++;
+ ln->ln_expire = time_second +
+ nd_ifinfo[ifp->if_index].retrans / 1000;
+ nd6_ns_output(ifp, NULL, &(SIN6(dst)->sin6_addr),
+ ln, 0);
+ }
+ }
+ return(0);
+}
+#endif /* OLDIP6OUTPUT */
+
void
nd6_rtrequest(req, rt, sa)
int req;
@@ -763,7 +1039,7 @@ nd6_rtrequest(req, rt, sa)
* SIN(rt_mask(rt))->sin_addr.s_addr != 0xffffffff)
* rt->rt_flags |= RTF_CLONING;
*/
- if (rt->rt_flags & RTF_CLONING || rt->rt_flags & RTF_LLINFO) {
+ if (rt->rt_flags & (RTF_CLONING | RTF_LLINFO)) {
/*
* Case 1: This route should come from
* a route to interface. RTF_LLINFO flag is set
@@ -777,30 +1053,63 @@ nd6_rtrequest(req, rt, sa)
SDL(gate)->sdl_index = ifp->if_index;
if (ln)
ln->ln_expire = time_second;
+#if 1
if (ln && ln->ln_expire == 0) {
/* cludge for desktops */
+#if 0
+ printf("nd6_request: time.tv_sec is zero; "
+ "treat it as 1\n");
+#endif
ln->ln_expire = 1;
}
+#endif
if (rt->rt_flags & RTF_CLONING)
break;
}
- /* Announce a new entry if requested. */
+ /*
+ * In IPv4 code, we try to annonuce new RTF_ANNOUNCE entry here.
+ * We don't do that here since llinfo is not ready yet.
+ *
+ * There are also couple of other things to be discussed:
+ * - unsolicited NA code needs improvement beforehand
+ * - RFC2461 says we MAY send multicast unsolicited NA
+ * (7.2.6 paragraph 4), however, it also says that we
+ * SHOULD provide a mechanism to prevent multicast NA storm.
+ * we don't have anything like it right now.
+ * note that the mechanism need a mutual agreement
+ * between proxies, which means that we need to implement
+ * a new protocol, or new kludge.
+ * - from RFC2461 6.2.4, host MUST NOT send unsolicited NA.
+ * we need to check ip6forwarding before sending it.
+ * (or should we allow proxy ND configuration only for
+ * routers? there's no mention about proxy ND from hosts)
+ */
+#if 0
+ /* XXX it does not work */
if (rt->rt_flags & RTF_ANNOUNCE)
nd6_na_output(ifp,
- &SIN6(rt_key(rt))->sin6_addr,
- &SIN6(rt_key(rt))->sin6_addr,
- ip6_forwarding ? ND_NA_FLAG_ROUTER : 0,
- 1);
+ &SIN6(rt_key(rt))->sin6_addr,
+ &SIN6(rt_key(rt))->sin6_addr,
+ ip6_forwarding ? ND_NA_FLAG_ROUTER : 0,
+ 1, NULL);
+#endif
/* FALLTHROUGH */
case RTM_RESOLVE:
- if (gate->sa_family != AF_LINK ||
- gate->sa_len < sizeof(null_sdl)) {
- log(LOG_DEBUG, "nd6_rtrequest: bad gateway value\n");
- break;
+ if ((ifp->if_flags & IFF_POINTOPOINT) == 0) {
+ /*
+ * Address resolution isn't necessary for a point to
+ * point link, so we can skip this test for a p2p link.
+ */
+ if (gate->sa_family != AF_LINK ||
+ gate->sa_len < sizeof(null_sdl)) {
+ log(LOG_DEBUG,
+ "nd6_rtrequest: bad gateway value\n");
+ break;
+ }
+ SDL(gate)->sdl_type = ifp->if_type;
+ SDL(gate)->sdl_index = ifp->if_index;
}
- SDL(gate)->sdl_type = ifp->if_type;
- SDL(gate)->sdl_index = ifp->if_index;
- if (ln != 0)
+ if (ln != NULL)
break; /* This happens on a route change */
/*
* Case 2: This route may come from cloning, or a manual route
@@ -824,12 +1133,13 @@ nd6_rtrequest(req, rt, sa)
* which is specified by ndp command.
*/
ln->ln_state = ND6_LLINFO_REACHABLE;
+ ln->ln_byhint = 0;
} else {
/*
* When req == RTM_RESOLVE, rt is created and
* initialized in rtrequest(), so rt_expire is 0.
*/
- ln->ln_state = ND6_LLINFO_INCOMPLETE;
+ ln->ln_state = ND6_LLINFO_NOSTATE;
ln->ln_expire = time_second;
}
rt->rt_flags |= RTF_LLINFO;
@@ -848,6 +1158,7 @@ nd6_rtrequest(req, rt, sa)
caddr_t macp = nd6_ifptomac(ifp);
ln->ln_expire = 0;
ln->ln_state = ND6_LLINFO_REACHABLE;
+ ln->ln_byhint = 0;
if (macp) {
Bcopy(macp, LLADDR(SDL(gate)), ifp->if_addrlen);
SDL(gate)->sdl_alen = ifp->if_addrlen;
@@ -863,17 +1174,56 @@ nd6_rtrequest(req, rt, sa)
* of the loopback address.
*/
if (ifa != rt->rt_ifa) {
- rt->rt_ifa->ifa_refcnt--;
+ IFAFREE(rt->rt_ifa);
ifa->ifa_refcnt++;
rt->rt_ifa = ifa;
}
}
+ } else if (rt->rt_flags & RTF_ANNOUNCE) {
+ ln->ln_expire = 0;
+ ln->ln_state = ND6_LLINFO_REACHABLE;
+ ln->ln_byhint = 0;
+
+ /* join solicited node multicast for proxy ND */
+ if (ifp->if_flags & IFF_MULTICAST) {
+ struct in6_addr llsol;
+ int error;
+
+ llsol = SIN6(rt_key(rt))->sin6_addr;
+ llsol.s6_addr16[0] = htons(0xff02);
+ llsol.s6_addr16[1] = htons(ifp->if_index);
+ llsol.s6_addr32[1] = 0;
+ llsol.s6_addr32[2] = htonl(1);
+ llsol.s6_addr8[12] = 0xff;
+
+ (void)in6_addmulti(&llsol, ifp, &error);
+ if (error)
+ printf(
+"nd6_rtrequest: could not join solicited node multicast (errno=%d)\n", error);
+ }
}
break;
case RTM_DELETE:
if (!ln)
break;
+ /* leave from solicited node multicast for proxy ND */
+ if ((rt->rt_flags & RTF_ANNOUNCE) != 0 &&
+ (ifp->if_flags & IFF_MULTICAST) != 0) {
+ struct in6_addr llsol;
+ struct in6_multi *in6m;
+
+ llsol = SIN6(rt_key(rt))->sin6_addr;
+ llsol.s6_addr16[0] = htons(0xff02);
+ llsol.s6_addr16[1] = htons(ifp->if_index);
+ llsol.s6_addr32[1] = 0;
+ llsol.s6_addr32[2] = htonl(1);
+ llsol.s6_addr8[12] = 0xff;
+
+ IN6_LOOKUP_MULTI(llsol, ifp, in6m);
+ if (in6m)
+ in6_delmulti(in6m);
+ }
nd6_inuse--;
ln->ln_next->ln_prev = ln->ln_prev;
ln->ln_prev->ln_next = ln->ln_next;
@@ -927,7 +1277,7 @@ nd6_p2p_rtrequest(req, rt, sa)
&SIN6(rt_key(rt))->sin6_addr,
&SIN6(rt_key(rt))->sin6_addr,
ip6_forwarding ? ND_NA_FLAG_ROUTER : 0,
- 1);
+ 1, NULL);
/* FALLTHROUGH */
case RTM_RESOLVE:
/*
@@ -955,6 +1305,7 @@ nd6_ioctl(cmd, data, ifp)
struct in6_prlist *prl = (struct in6_prlist *)data;
struct in6_ndireq *ndi = (struct in6_ndireq *)data;
struct in6_nbrinfo *nbi = (struct in6_nbrinfo *)data;
+ struct in6_ndifreq *ndif = (struct in6_ndifreq *)data;
struct nd_defrouter *dr, any;
struct nd_prefix *pr;
struct rtentry *rt;
@@ -965,7 +1316,7 @@ nd6_ioctl(cmd, data, ifp)
case SIOCGDRLST_IN6:
bzero(drl, sizeof(*drl));
s = splnet();
- dr = LIST_FIRST(&nd_defrouter);
+ dr = TAILQ_FIRST(&nd_defrouter);
while (dr && i < DRLSTSIZ) {
drl->defrouter[i].rtaddr = dr->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&drl->defrouter[i].rtaddr)) {
@@ -982,14 +1333,19 @@ nd6_ioctl(cmd, data, ifp)
drl->defrouter[i].expire = dr->expire;
drl->defrouter[i].if_index = dr->ifp->if_index;
i++;
- dr = LIST_NEXT(dr, dr_entry);
+ dr = TAILQ_NEXT(dr, dr_entry);
}
splx(s);
break;
case SIOCGPRLST_IN6:
+ /*
+ * XXX meaning of fields, especialy "raflags", is very
+ * differnet between RA prefix list and RR/static prefix list.
+ * how about separating ioctls into two?
+ */
bzero(prl, sizeof(*prl));
s = splnet();
- pr = LIST_FIRST(&nd_prefix);
+ pr = nd_prefix.lh_first;
while (pr && i < PRLSTSIZ) {
struct nd_pfxrouter *pfr;
int j;
@@ -1002,7 +1358,7 @@ nd6_ioctl(cmd, data, ifp)
prl->prefix[i].if_index = pr->ndpr_ifp->if_index;
prl->prefix[i].expire = pr->ndpr_expire;
- pfr = LIST_FIRST(&pr->ndpr_advrtrs);
+ pfr = pr->ndpr_advrtrs.lh_first;
j = 0;
while(pfr) {
if (j < DRLSTSIZ) {
@@ -1020,14 +1376,14 @@ nd6_ioctl(cmd, data, ifp)
#undef RTRADDR
}
j++;
- pfr = LIST_NEXT(pfr, pfr_entry);
+ pfr = pfr->pfr_next;
}
prl->prefix[i].advrtrs = j;
+ prl->prefix[i].origin = PR_ORIG_RA;
i++;
- pr = LIST_NEXT(pr, ndpr_entry);
+ pr = pr->ndpr_next;
}
- splx(s);
{
struct rr_prefix *rpp;
@@ -1043,15 +1399,29 @@ nd6_ioctl(cmd, data, ifp)
prl->prefix[i].if_index = rpp->rp_ifp->if_index;
prl->prefix[i].expire = rpp->rp_expire;
prl->prefix[i].advrtrs = 0;
+ prl->prefix[i].origin = rpp->rp_origin;
i++;
}
}
+ splx(s);
break;
case SIOCGIFINFO_IN6:
+ if (!nd_ifinfo || i >= nd_ifinfo_indexlim) {
+ error = EINVAL;
+ break;
+ }
ndi->ndi = nd_ifinfo[ifp->if_index];
break;
- case SIOCSNDFLUSH_IN6:
+ case SIOCSIFINFO_FLAGS:
+ /* XXX: almost all other fields of ndi->ndi is unused */
+ if (!nd_ifinfo || i >= nd_ifinfo_indexlim) {
+ error = EINVAL;
+ break;
+ }
+ nd_ifinfo[ifp->if_index].flags = ndi->ndi.flags;
+ break;
+ case SIOCSNDFLUSH_IN6: /* XXX: the ioctl name is confusing... */
/* flush default router list */
/*
* xxx sumikawa: should not delete route if default
@@ -1059,6 +1429,7 @@ nd6_ioctl(cmd, data, ifp)
*/
bzero(&any, sizeof(any));
defrouter_delreq(&any, 0);
+ defrouter_select();
/* xxx sumikawa: flush prefix list */
break;
case SIOCSPFXFLUSH_IN6:
@@ -1067,8 +1438,8 @@ nd6_ioctl(cmd, data, ifp)
struct nd_prefix *pr, *next;
s = splnet();
- for (pr = LIST_FIRST(&nd_prefix); pr; pr = next) {
- next = LIST_NEXT(pr, ndpr_entry);
+ for (pr = nd_prefix.lh_first; pr; pr = next) {
+ next = pr->ndpr_next;
if (!IN6_IS_ADDR_UNSPECIFIED(&pr->ndpr_addr))
in6_ifdel(pr->ndpr_ifp, &pr->ndpr_addr);
prelist_remove(pr);
@@ -1082,16 +1453,16 @@ nd6_ioctl(cmd, data, ifp)
struct nd_defrouter *dr, *next;
s = splnet();
- if ((dr = LIST_FIRST(&nd_defrouter)) != NULL) {
+ if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
/*
* The first entry of the list may be stored in
* the routing table, so we'll delete it later.
*/
- for (dr = LIST_NEXT(dr, dr_entry); dr; dr = next) {
- next = LIST_NEXT(dr, dr_entry);
+ for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = next) {
+ next = TAILQ_NEXT(dr, dr_entry);
defrtrlist_del(dr);
}
- defrtrlist_del(LIST_FIRST(&nd_defrouter));
+ defrtrlist_del(TAILQ_FIRST(&nd_defrouter));
}
splx(s);
break;
@@ -1116,6 +1487,7 @@ nd6_ioctl(cmd, data, ifp)
s = splnet();
if ((rt = nd6_lookup(&nb_addr, 0, ifp)) == NULL) {
error = EINVAL;
+ splx(s);
break;
}
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
@@ -1127,6 +1499,12 @@ nd6_ioctl(cmd, data, ifp)
break;
}
+ case SIOCGDEFIFACE_IN6: /* XXX: should be implemented as a sysctl? */
+ ndif->ifindex = nd6_defifindex;
+ break;
+ case SIOCSDEFIFACE_IN6: /* XXX: should be implemented as a sysctl? */
+ return(nd6_setdefaultiface(ndif->ifindex));
+ break;
}
return(error);
}
@@ -1174,6 +1552,12 @@ nd6_cache_lladdr(ifp, from, lladdr, lladdrlen, type, code)
rt = nd6_lookup(from, 0, ifp);
if (!rt) {
+#if 0
+ /* nothing must be done if there's no lladdr */
+ if (!lladdr || !lladdrlen)
+ return NULL;
+#endif
+
rt = nd6_lookup(from, 1, ifp);
is_newentry = 1;
} else
@@ -1248,9 +1632,18 @@ fail:
if (ln->ln_state == ND6_LLINFO_STALE) {
rt->rt_flags &= ~RTF_REJECT;
if (ln->ln_hold) {
- nd6_output(ifp, ln->ln_hold,
+#ifdef OLDIP6OUTPUT
+ (*ifp->if_output)(ifp, ln->ln_hold,
+ rt_key(rt), rt);
+#else
+ /*
+ * we assume ifp is not a p2p here, so just
+ * set the 2nd argument as the 1st one.
+ */
+ nd6_output(ifp, ifp, ln->ln_hold,
(struct sockaddr_in6 *)rt_key(rt),
rt);
+#endif
ln->ln_hold = 0;
}
} else if (ln->ln_state == ND6_LLINFO_INCOMPLETE) {
@@ -1301,7 +1694,6 @@ fail:
* If the icmp is a redirect to a better router, always set the
* is_router flag. Otherwise, if the entry is newly created,
* clear the flag. [RFC 2461, sec 8.3]
- *
*/
if (code == ND_REDIRECT_ROUTER)
ln->ln_router = 1;
@@ -1338,6 +1730,8 @@ nd6_slowtimo(ignored_arg)
timeout(nd6_slowtimo, (caddr_t)0, ND6_SLOWTIMER_INTERVAL * hz);
for (i = 1; i < if_index + 1; i++) {
+ if (!nd_ifinfo || i >= nd_ifinfo_indexlim)
+ continue;
nd6if = &nd_ifinfo[i];
if (nd6if->basereachable && /* already initialized */
(nd6if->recalctm -= ND6_SLOWTIMER_INTERVAL) <= 0) {
@@ -1356,14 +1750,16 @@ nd6_slowtimo(ignored_arg)
#define senderr(e) { error = (e); goto bad;}
int
-nd6_output(ifp, m0, dst, rt0)
+nd6_output(ifp, origifp, m0, dst, rt0)
register struct ifnet *ifp;
+ struct ifnet *origifp;
struct mbuf *m0;
struct sockaddr_in6 *dst;
struct rtentry *rt0;
{
register struct mbuf *m = m0;
register struct rtentry *rt = rt0;
+ struct sockaddr_in6 *gw6 = NULL;
struct llinfo_nd6 *ln = NULL;
int error = 0;
@@ -1372,12 +1768,16 @@ nd6_output(ifp, m0, dst, rt0)
/*
* XXX: we currently do not make neighbor cache on any interface
- * other than ARCnet, Ethernet and FDDI.
+ * other than ARCnet, Ethernet, FDDI and GIF.
+ *
+ * draft-ietf-ngtrans-mech-06.txt says:
+ * - unidirectional tunnels needs no ND
*/
switch (ifp->if_type) {
case IFT_ARCNET:
case IFT_ETHER:
case IFT_FDDI:
+ case IFT_GIF: /* XXX need more cases? */
break;
default:
goto sendpkt;
@@ -1392,12 +1792,43 @@ nd6_output(ifp, m0, dst, rt0)
NULL)
{
rt->rt_refcnt--;
- if (rt->rt_ifp != ifp)
- return nd6_output(ifp, m0, dst, rt); /* XXX: loop care? */
+ if (rt->rt_ifp != ifp) {
+ /* XXX: loop care? */
+ return nd6_output(ifp, origifp, m0,
+ dst, rt);
+ }
} else
senderr(EHOSTUNREACH);
}
+
if (rt->rt_flags & RTF_GATEWAY) {
+ gw6 = (struct sockaddr_in6 *)rt->rt_gateway;
+
+ /*
+ * We skip link-layer address resolution and NUD
+ * if the gateway is not a neighbor from ND point
+ * of view, regardless the value of the value of
+ * nd_ifinfo.flags.
+ * The second condition is a bit tricky: we skip
+ * if the gateway is our own address, which is
+ * sometimes used to install a route to a p2p link.
+ */
+ if (!nd6_is_addr_neighbor(gw6, ifp) ||
+ in6ifa_ifpwithaddr(ifp, &gw6->sin6_addr)) {
+ if (rt->rt_flags & RTF_REJECT)
+ senderr(EHOSTDOWN);
+
+ /*
+ * We allow this kind of tricky route only
+ * when the outgoing interface is p2p.
+ * XXX: we may need a more generic rule here.
+ */
+ if ((ifp->if_flags & IFF_POINTOPOINT) == 0)
+ senderr(EHOSTUNREACH);
+
+ goto sendpkt;
+ }
+
if (rt->rt_gwroute == 0)
goto lookup;
if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) {
@@ -1422,16 +1853,32 @@ nd6_output(ifp, m0, dst, rt0)
if (rt && (rt->rt_flags & RTF_LLINFO) != 0)
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
else {
- if ((rt = nd6_lookup(&dst->sin6_addr, 1, ifp)) != NULL)
+ /*
+ * Since nd6_is_addr_neighbor() internally calls nd6_lookup(),
+ * the condition below is not very efficient. But we believe
+ * it is tolerable, because this should be a rare case.
+ */
+ if (nd6_is_addr_neighbor(dst, ifp) &&
+ (rt = nd6_lookup(&dst->sin6_addr, 1, ifp)) != NULL)
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
}
if (!ln || !rt) {
- log(LOG_DEBUG, "nd6_output: can't allocate llinfo for %s "
- "(ln=%p, rt=%p)\n",
- ip6_sprintf(&dst->sin6_addr), ln, rt);
- senderr(EIO); /* XXX: good error? */
+ if ((ifp->if_flags & IFF_POINTOPOINT) == 0 &&
+ !(nd_ifinfo[ifp->if_index].flags & ND6_IFF_PERFORMNUD)) {
+ log(LOG_DEBUG,
+ "nd6_output: can't allocate llinfo for %s "
+ "(ln=%p, rt=%p)\n",
+ ip6_sprintf(&dst->sin6_addr), ln, rt);
+ senderr(EIO); /* XXX: good error? */
+ }
+
+ goto sendpkt; /* send anyway */
}
+ /* We don't have to do link-layer address resolution on a p2p link. */
+ if ((ifp->if_flags & IFF_POINTOPOINT) != 0 &&
+ ln->ln_state < ND6_LLINFO_REACHABLE)
+ ln->ln_state = ND6_LLINFO_STALE;
/*
* The first time we send a packet to a neighbor whose entry is
@@ -1481,8 +1928,15 @@ nd6_output(ifp, m0, dst, rt0)
return(0);
sendpkt:
+
+#ifdef FAKE_LOOPBACK_IF
+ if (ifp->if_flags & IFF_LOOPBACK) {
+ return((*ifp->if_output)(origifp, m, (struct sockaddr *)dst,
+ rt));
+ }
+#endif
return((*ifp->if_output)(ifp, m, (struct sockaddr *)dst, rt));
-
+
bad:
if (m)
m_freem(m);
@@ -1522,8 +1976,12 @@ nd6_storelladdr(ifp, rt, m, dst, desten)
return(0);
}
sdl = SDL(rt->rt_gateway);
- if (sdl->sdl_alen != 0)
- bcopy(LLADDR(sdl), desten, sdl->sdl_alen);
+ if (sdl->sdl_alen == 0) {
+ /* this should be impossible, but we bark here for debugging */
+ printf("nd6_storelladdr: sdl_alen == 0\n");
+ return(0);
+ }
+ bcopy(LLADDR(sdl), desten, sdl->sdl_alen);
return(1);
}
OpenPOWER on IntegriCloud