Module Name:    src
Committed By:   ozaki-r
Date:           Wed Nov 25 06:21:26 UTC 2015

Modified Files:
        src/sys/net: if_llatbl.c if_llatbl.h
        src/sys/netinet6: in6.c in6_var.h nd6.c nd6.h nd6_nbr.c nd6_rtr.c

Log Message:
Use lltable/llentry for NDP

lltable and llentry were introduced to replace ARP cache data structure
for further restructuring of the routing table: L2 nexthop cache
separation. This change replaces the NDP cache data structure
(llinfo_nd6) with them as well as ARP.

One noticeable change is for neighbor cache GC mechanism that was
introduced to prevent IPv6 DoS attacks. net.inet6.ip6.neighborgcthresh
was the max number of caches that we store in the system. After
introducing lltable/llentry, the value is changed to be per-interface
basis because lltable/llentry stores neighbor caches in each interface
separately. And the change brings one degradation; the old GC mechanism
dropped exceeded packets based on LRU while the new implementation drops
packets in order from the beginning of lltable (a hash table + linked
lists). It would be improved in the future.

Added functions in in6.c come from FreeBSD (as of r286629) and are
tweaked for NetBSD.

Proposed on tech-kern and tech-net.


To generate a diff of this commit:
cvs rdiff -u -r1.7 -r1.8 src/sys/net/if_llatbl.c
cvs rdiff -u -r1.5 -r1.6 src/sys/net/if_llatbl.h
cvs rdiff -u -r1.190 -r1.191 src/sys/netinet6/in6.c
cvs rdiff -u -r1.74 -r1.75 src/sys/netinet6/in6_var.h
cvs rdiff -u -r1.180 -r1.181 src/sys/netinet6/nd6.c
cvs rdiff -u -r1.67 -r1.68 src/sys/netinet6/nd6.h
cvs rdiff -u -r1.111 -r1.112 src/sys/netinet6/nd6_nbr.c
cvs rdiff -u -r1.104 -r1.105 src/sys/netinet6/nd6_rtr.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/net/if_llatbl.c
diff -u src/sys/net/if_llatbl.c:1.7 src/sys/net/if_llatbl.c:1.8
--- src/sys/net/if_llatbl.c:1.7	Tue Oct 20 07:35:15 2015
+++ src/sys/net/if_llatbl.c	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_llatbl.c,v 1.7 2015/10/20 07:35:15 ozaki-r Exp $	*/
+/*	$NetBSD: if_llatbl.c,v 1.8 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*
  * Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved.
  * Copyright (c) 2004-2008 Qing Li. All rights reserved.
@@ -162,6 +162,8 @@ htable_link_entry(struct lltable *llt, s
 	lle->lle_head = lleh;
 	lle->la_flags |= LLE_LINKED;
 	LIST_INSERT_HEAD(lleh, lle, lle_next);
+
+	llt->llt_lle_count++;
 }
 
 static void
@@ -176,6 +178,8 @@ htable_unlink_entry(struct llentry *lle)
 		lle->lle_tbl = NULL;
 		lle->lle_head = NULL;
 #endif
+		KASSERT(lle->lle_tbl->llt_lle_count != 0);
+		lle->lle_tbl->llt_lle_count--;
 	}
 }
 
@@ -352,18 +356,16 @@ lltable_free_cb(struct lltable *llt, str
 }
 
 /*
- * Free all entries from given table and free itself.
+ * Free all entries from given table.
  */
 void
-lltable_free(struct lltable *llt)
+lltable_purge_entries(struct lltable *llt)
 {
 	struct llentry *lle, *next;
 	struct llentries dchain;
 
 	KASSERTMSG(llt != NULL, "llt is NULL");
 
-	lltable_unlink(llt);
-
 	LIST_INIT(&dchain);
 	IF_AFDATA_WLOCK(llt->llt_ifp);
 	/* Push all lles to @dchain */
@@ -394,6 +396,19 @@ lltable_free(struct lltable *llt)
 		llentry_free(lle);
 	}
 
+}
+
+/*
+ * Free all entries from given table and free itself.
+ */
+void
+lltable_free(struct lltable *llt)
+{
+
+	KASSERTMSG(llt != NULL, "llt is NULL");
+
+	lltable_unlink(llt);
+	lltable_purge_entries(llt);
 	llt->llt_free_tbl(llt);
 }
 

Index: src/sys/net/if_llatbl.h
diff -u src/sys/net/if_llatbl.h:1.5 src/sys/net/if_llatbl.h:1.6
--- src/sys/net/if_llatbl.h:1.5	Thu Nov  5 06:50:51 2015
+++ src/sys/net/if_llatbl.h	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_llatbl.h,v 1.5 2015/11/05 06:50:51 ozaki-r Exp $	*/
+/*	$NetBSD: if_llatbl.h,v 1.6 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*
  * Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved.
  * Copyright (c) 2004-2008 Qing Li. All rights reserved.
@@ -101,7 +101,12 @@ struct llentry {
 
 #ifdef __NetBSD__
 #define	la_timer	lle_timer
+#define	ln_timer_ch	lle_timer
+#define	ln_expire	la_expire
+#define	ln_asked	la_asked
+#define	ln_hold		la_hold
 	struct rtentry		*la_rt;
+#define	ln_rt		la_rt
 	void			*la_opaque;	/* For tokenring */
 #endif
 };
@@ -240,6 +245,7 @@ struct lltable {
 	int			llt_af;
 	int			llt_hsize;
 	struct llentries	*lle_head;
+	unsigned int		llt_lle_count;
 	struct ifnet		*llt_ifp;
 
 	llt_lookup_t		*llt_lookup;
@@ -282,6 +288,7 @@ void		lltable_link(struct lltable *llt);
 void		lltable_prefix_free(int, struct sockaddr *,
 		    struct sockaddr *, u_int);
 void		lltable_drain(int);
+void		lltable_purge_entries(struct lltable *);
 int		lltable_sysctl_dumparp(int, struct sysctl_req *);
 
 size_t		llentry_free(struct llentry *);
@@ -299,6 +306,12 @@ void lltable_fill_sa_entry(const struct 
 struct ifnet *lltable_get_ifp(const struct lltable *llt);
 int lltable_get_af(const struct lltable *llt);
 
+static inline unsigned int
+lltable_get_entry_count(struct lltable *llt)
+{
+	return llt->llt_lle_count;
+}
+
 int lltable_foreach_lle(struct lltable *llt, llt_foreach_cb_t *f,
     void *farg);
 /*

Index: src/sys/netinet6/in6.c
diff -u src/sys/netinet6/in6.c:1.190 src/sys/netinet6/in6.c:1.191
--- src/sys/netinet6/in6.c:1.190	Mon Aug 24 22:21:27 2015
+++ src/sys/netinet6/in6.c	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6.c,v 1.190 2015/08/24 22:21:27 pooka Exp $	*/
+/*	$NetBSD: in6.c,v 1.191 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: in6.c,v 1.198 2001/07/18 09:12:38 itojun Exp $	*/
 
 /*
@@ -62,7 +62,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.190 2015/08/24 22:21:27 pooka Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.191 2015/11/25 06:21:26 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -83,16 +83,18 @@ __KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.19
 #include <sys/syslog.h>
 #include <sys/kauth.h>
 #include <sys/cprng.h>
+#include <sys/kmem.h>
 
 #include <net/if.h>
 #include <net/if_types.h>
-#include <net/route.h>
+#include <net/if_llatbl.h>
+#include <net/if_ether.h>
 #include <net/if_dl.h>
 #include <net/pfil.h>
+#include <net/route.h>
 
 #include <netinet/in.h>
 #include <netinet/in_var.h>
-#include <net/if_ether.h>
 
 #include <netinet/ip6.h>
 #include <netinet6/ip6_var.h>
@@ -1761,6 +1763,34 @@ in6ifa_ifpforlinklocal(const struct ifne
 	return (struct in6_ifaddr *)best_ifa;
 }
 
+/*
+ * find the internet address corresponding to a given address.
+ * ifaddr is returned referenced.
+ */
+struct in6_ifaddr *
+in6ifa_ifwithaddr(const struct in6_addr *addr, uint32_t zoneid)
+{
+	struct in6_ifaddr *ia;
+
+#ifdef __FreeBSD__
+	IN6_IFADDR_RLOCK();
+	LIST_FOREACH(ia, IN6ADDR_HASH(addr), ia6_hash) {
+#else
+	for (ia = in6_ifaddr; ia; ia = ia->ia_next) {
+#endif
+		if (IN6_ARE_ADDR_EQUAL(IA6_IN6(ia), addr)) {
+			if (zoneid != 0 &&
+			    zoneid != ia->ia_addr.sin6_scope_id)
+				continue;
+			ifaref(&ia->ia_ifa);
+			break;
+		}
+	}
+#ifdef __FreeBSD__
+	IN6_IFADDR_RUNLOCK();
+#endif
+	return ia;
+}
 
 /*
  * find the internet address corresponding to a given interface and address.
@@ -2196,6 +2226,287 @@ in6_if2idlen(struct ifnet *ifp)
 	}
 }
 
+struct in6_llentry {
+	struct llentry		base;
+};
+
+#define	IN6_LLTBL_DEFAULT_HSIZE	32
+#define	IN6_LLTBL_HASH(k, h) \
+	(((((((k >> 8) ^ k) >> 8) ^ k) >> 8) ^ k) & ((h) - 1))
+
+/*
+ * Do actual deallocation of @lle.
+ * Called by LLE_FREE_LOCKED when number of references
+ * drops to zero.
+ */
+static void
+in6_lltable_destroy_lle(struct llentry *lle)
+{
+
+	LLE_WUNLOCK(lle);
+	LLE_LOCK_DESTROY(lle);
+	kmem_intr_free(lle, sizeof(struct in6_llentry));
+}
+
+static struct llentry *
+in6_lltable_new(const struct in6_addr *addr6, u_int flags)
+{
+	struct in6_llentry *lle;
+
+	lle = kmem_intr_zalloc(sizeof(struct in6_llentry), KM_NOSLEEP);
+	if (lle == NULL)		/* NB: caller generates msg */
+		return NULL;
+
+	lle->base.r_l3addr.addr6 = *addr6;
+	lle->base.lle_refcnt = 1;
+	lle->base.lle_free = in6_lltable_destroy_lle;
+	LLE_LOCK_INIT(&lle->base);
+	callout_init(&lle->base.lle_timer, CALLOUT_MPSAFE);
+
+	return &lle->base;
+}
+
+static int
+in6_lltable_match_prefix(const struct sockaddr *prefix,
+    const struct sockaddr *mask, u_int flags, struct llentry *lle)
+{
+	const struct sockaddr_in6 *pfx = (const struct sockaddr_in6 *)prefix;
+	const struct sockaddr_in6 *msk = (const struct sockaddr_in6 *)mask;
+
+	if (IN6_ARE_MASKED_ADDR_EQUAL(&lle->r_l3addr.addr6,
+	    &pfx->sin6_addr, &msk->sin6_addr) &&
+	    ((flags & LLE_STATIC) || !(lle->la_flags & LLE_STATIC)))
+		return 1;
+
+	return 0;
+}
+
+static void
+in6_lltable_free_entry(struct lltable *llt, struct llentry *lle)
+{
+	struct ifnet *ifp;
+
+	LLE_WLOCK_ASSERT(lle);
+	KASSERT(llt != NULL);
+
+	/* Unlink entry from table */
+	if ((lle->la_flags & LLE_LINKED) != 0) {
+
+		ifp = llt->llt_ifp;
+		IF_AFDATA_WLOCK_ASSERT(ifp);
+		lltable_unlink_entry(llt, lle);
+	}
+
+	KASSERT(mutex_owned(softnet_lock));
+	callout_halt(&lle->lle_timer, softnet_lock);
+	LLE_REMREF(lle);
+
+	llentry_free(lle);
+}
+
+static int
+in6_lltable_rtcheck(struct ifnet *ifp,
+		    u_int flags,
+		    const struct sockaddr *l3addr)
+{
+	struct rtentry *rt;
+
+	KASSERTMSG(l3addr->sa_family == AF_INET6,
+	    "sin_family %d", l3addr->sa_family);
+
+	rt = rtalloc1(l3addr, 0);
+	if (rt == NULL || (rt->rt_flags & RTF_GATEWAY) || rt->rt_ifp != ifp) {
+		struct ifaddr *ifa;
+		/*
+		 * Create an ND6 cache for an IPv6 neighbor
+		 * that is not covered by our own prefix.
+		 */
+		/* XXX ifaof_ifpforaddr should take a const param */
+		ifa = ifaof_ifpforaddr(l3addr, ifp);
+		if (ifa != NULL) {
+			ifafree(ifa);
+			if (rt != NULL)
+				rtfree(rt);
+			return 0;
+		}
+		log(LOG_INFO, "IPv6 address: \"%s\" is not on the network\n",
+		    ip6_sprintf(&((const struct sockaddr_in6 *)l3addr)->sin6_addr));
+		if (rt != NULL)
+			rtfree(rt);
+		return EINVAL;
+	}
+	rtfree(rt);
+	return 0;
+}
+
+static inline uint32_t
+in6_lltable_hash_dst(const struct in6_addr *dst, uint32_t hsize)
+{
+
+	return IN6_LLTBL_HASH(dst->s6_addr32[3], hsize);
+}
+
+static uint32_t
+in6_lltable_hash(const struct llentry *lle, uint32_t hsize)
+{
+
+	return in6_lltable_hash_dst(&lle->r_l3addr.addr6, hsize);
+}
+
+static void
+in6_lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa)
+{
+	struct sockaddr_in6 *sin6;
+
+	sin6 = (struct sockaddr_in6 *)sa;
+	bzero(sin6, sizeof(*sin6));
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_len = sizeof(*sin6);
+	sin6->sin6_addr = lle->r_l3addr.addr6;
+}
+
+static inline struct llentry *
+in6_lltable_find_dst(struct lltable *llt, const struct in6_addr *dst)
+{
+	struct llentry *lle;
+	struct llentries *lleh;
+	u_int hashidx;
+
+	hashidx = in6_lltable_hash_dst(dst, llt->llt_hsize);
+	lleh = &llt->lle_head[hashidx];
+	LIST_FOREACH(lle, lleh, lle_next) {
+		if (lle->la_flags & LLE_DELETED)
+			continue;
+		if (IN6_ARE_ADDR_EQUAL(&lle->r_l3addr.addr6, dst))
+			break;
+	}
+
+	return lle;
+}
+
+static int
+in6_lltable_delete(struct lltable *llt, u_int flags,
+	const struct sockaddr *l3addr)
+{
+	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr;
+	struct llentry *lle;
+
+	IF_AFDATA_WLOCK_ASSERT(llt->llt_ifp);
+	KASSERTMSG(l3addr->sa_family == AF_INET6,
+	    "sin_family %d", l3addr->sa_family);
+
+	lle = in6_lltable_find_dst(llt, &sin6->sin6_addr);
+
+	if (lle == NULL)
+		return ENOENT;
+
+	if (!(lle->la_flags & LLE_IFADDR) || (flags & LLE_IFADDR)) {
+		LLE_WLOCK(lle);
+		lle->la_flags |= LLE_DELETED;
+#ifdef DIAGNOSTIC
+		log(LOG_INFO, "ifaddr cache = %p is deleted\n", lle);
+#endif
+		if ((lle->la_flags & (LLE_STATIC | LLE_IFADDR)) == LLE_STATIC)
+			llentry_free(lle);
+		else
+			LLE_WUNLOCK(lle);
+	}
+
+	return 0;
+}
+
+static struct llentry *
+in6_lltable_create(struct lltable *llt, u_int flags,
+	const struct sockaddr *l3addr)
+{
+	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr;
+	struct ifnet *ifp = llt->llt_ifp;
+	struct llentry *lle;
+
+	IF_AFDATA_WLOCK_ASSERT(ifp);
+	KASSERTMSG(l3addr->sa_family == AF_INET6,
+	    "sin_family %d", l3addr->sa_family);
+
+	lle = in6_lltable_find_dst(llt, &sin6->sin6_addr);
+
+	if (lle != NULL) {
+		LLE_WLOCK(lle);
+		return lle;
+	}
+
+	/*
+	 * A route that covers the given address must have
+	 * been installed 1st because we are doing a resolution,
+	 * verify this.
+	 */
+	if (!(flags & LLE_IFADDR) &&
+	    in6_lltable_rtcheck(ifp, flags, l3addr) != 0)
+		return NULL;
+
+	lle = in6_lltable_new(&sin6->sin6_addr, flags);
+	if (lle == NULL) {
+		log(LOG_INFO, "lla_lookup: new lle malloc failed\n");
+		return NULL;
+	}
+	lle->la_flags = flags;
+	if ((flags & LLE_IFADDR) == LLE_IFADDR) {
+		memcpy(&lle->ll_addr, CLLADDR(ifp->if_sadl), ifp->if_addrlen);
+		lle->la_flags |= (LLE_VALID | LLE_STATIC);
+	}
+
+	lltable_link_entry(llt, lle);
+	LLE_WLOCK(lle);
+
+	return lle;
+}
+
+static struct llentry *
+in6_lltable_lookup(struct lltable *llt, u_int flags,
+	const struct sockaddr *l3addr)
+{
+	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr;
+	struct llentry *lle;
+
+	IF_AFDATA_LOCK_ASSERT(llt->llt_ifp);
+	KASSERTMSG(l3addr->sa_family == AF_INET6,
+	    "sin_family %d", l3addr->sa_family);
+
+	lle = in6_lltable_find_dst(llt, &sin6->sin6_addr);
+
+	if (lle == NULL)
+		return NULL;
+
+	if (flags & LLE_EXCLUSIVE)
+		LLE_WLOCK(lle);
+	else
+		LLE_RLOCK(lle);
+	return lle;
+}
+
+static struct lltable *
+in6_lltattach(struct ifnet *ifp)
+{
+	struct lltable *llt;
+
+	llt = lltable_allocate_htbl(IN6_LLTBL_DEFAULT_HSIZE);
+	llt->llt_af = AF_INET6;
+	llt->llt_ifp = ifp;
+
+	llt->llt_lookup = in6_lltable_lookup;
+	llt->llt_create = in6_lltable_create;
+	llt->llt_delete = in6_lltable_delete;
+#if notyet
+	llt->llt_dump_entry = in6_lltable_dump_entry;
+#endif
+	llt->llt_hash = in6_lltable_hash;
+	llt->llt_fill_sa_entry = in6_lltable_fill_sa_entry;
+	llt->llt_free_entry = in6_lltable_free_entry;
+	llt->llt_match_prefix = in6_lltable_match_prefix;
+	lltable_link(llt);
+
+	return llt;
+}
+
 void *
 in6_domifattach(struct ifnet *ifp)
 {
@@ -2213,6 +2524,9 @@ in6_domifattach(struct ifnet *ifp)
 	ext->scope6_id = scope6_ifattach(ifp);
 	ext->nprefixes = 0;
 	ext->ndefrouters = 0;
+
+	ext->lltable = in6_lltattach(ifp);
+
 	return ext;
 }
 
@@ -2221,6 +2535,8 @@ in6_domifdetach(struct ifnet *ifp, void 
 {
 	struct in6_ifextra *ext = (struct in6_ifextra *)aux;
 
+	lltable_free(ext->lltable);
+	ext->lltable = NULL;
 	nd6_ifdetach(ifp, ext);
 	free(ext->in6_ifstat, M_IFADDR);
 	free(ext->icmp6_ifstat, M_IFADDR);

Index: src/sys/netinet6/in6_var.h
diff -u src/sys/netinet6/in6_var.h:1.74 src/sys/netinet6/in6_var.h:1.75
--- src/sys/netinet6/in6_var.h:1.74	Sun Sep  6 06:01:01 2015
+++ src/sys/netinet6/in6_var.h	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6_var.h,v 1.74 2015/09/06 06:01:01 dholland Exp $	*/
+/*	$NetBSD: in6_var.h,v 1.75 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: in6_var.h,v 1.81 2002/06/08 11:16:51 itojun Exp $	*/
 
 /*
@@ -89,6 +89,7 @@ struct in6_addrlifetime {
 	u_int32_t ia6t_pltime;	/* prefix lifetime */
 };
 
+struct lltable;
 struct nd_ifinfo;
 struct in6_ifextra {
 	struct in6_ifstat *in6_ifstat;
@@ -97,6 +98,7 @@ struct in6_ifextra {
 	struct scope6_id *scope6_id;
 	int nprefixes;
 	int ndefrouters;
+	struct lltable *lltable;
 };
 
 LIST_HEAD(in6_multihead, in6_multi);
@@ -700,6 +702,7 @@ void	in6_purgemkludge(struct ifnet *);
 struct in6_ifaddr *in6ifa_ifpforlinklocal(const struct ifnet *, int);
 struct in6_ifaddr *in6ifa_ifpwithaddr(const struct ifnet *,
     const struct in6_addr *);
+struct in6_ifaddr *in6ifa_ifwithaddr(const struct in6_addr *, uint32_t);
 char	*ip6_sprintf(const struct in6_addr *);
 int	in6_matchlen(struct in6_addr *, struct in6_addr *);
 int	in6_are_prefix_equal(struct in6_addr *, struct in6_addr *, int);
@@ -711,6 +714,9 @@ int	ip6flow_fastforward(struct mbuf **);
 int in6_src_ioctl(u_long, void *);
 int	in6_is_addr_deprecated(struct sockaddr_in6 *);
 struct in6pcb;
+
+#define	LLTABLE6(ifp)	(((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->lltable)
+
 #endif /* _KERNEL */
 
 #endif /* !_NETINET6_IN6_VAR_H_ */

Index: src/sys/netinet6/nd6.c
diff -u src/sys/netinet6/nd6.c:1.180 src/sys/netinet6/nd6.c:1.181
--- src/sys/netinet6/nd6.c:1.180	Thu Nov 19 03:02:10 2015
+++ src/sys/netinet6/nd6.c	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.c,v 1.180 2015/11/19 03:02:10 ozaki-r Exp $	*/
+/*	$NetBSD: nd6.c,v 1.181 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: nd6.c,v 1.279 2002/06/08 11:16:51 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.180 2015/11/19 03:02:10 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.181 2015/11/25 06:21:26 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_net_mpsafe.h"
@@ -59,6 +59,7 @@ __KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.18
 
 #include <net/if.h>
 #include <net/if_dl.h>
+#include <net/if_llatbl.h>
 #include <net/if_types.h>
 #include <net/route.h>
 #include <net/if_ether.h>
@@ -104,10 +105,6 @@ int nd6_debug = 0;
 /* for debugging? */
 static int nd6_inuse, nd6_allocated;
 
-struct llinfo_nd6 llinfo_nd6 = {
-	.ln_prev = &llinfo_nd6,
-	.ln_next = &llinfo_nd6,
-};
 struct nd_drhead nd_defrouter;
 struct nd_prhead nd_prefix = { 0 };
 
@@ -124,9 +121,9 @@ static const struct sockaddr_in6 all1_sa
 static void nd6_setmtu0(struct ifnet *, struct nd_ifinfo *);
 static void nd6_slowtimo(void *);
 static int regen_tmpaddr(struct in6_ifaddr *);
-static struct llinfo_nd6 *nd6_free(struct rtentry *, int);
+static void nd6_free(struct rtentry *, struct llentry *, int);
 static void nd6_llinfo_timer(void *);
-static void clear_llinfo_pqueue(struct llinfo_nd6 *);
+static void clear_llinfo_pqueue(struct llentry *);
 
 callout_t nd6_slowtimo_ch;
 callout_t nd6_timer_ch;
@@ -137,16 +134,6 @@ static int fill_prlist(void *, size_t *,
 
 MALLOC_DEFINE(M_IP6NDP, "NDP", "IPv6 Neighbour Discovery");
 
-#define LN_DEQUEUE(ln) do { \
-	(ln)->ln_next->ln_prev = (ln)->ln_prev; \
-	(ln)->ln_prev->ln_next = (ln)->ln_next; \
-	} while (/*CONSTCOND*/0)
-#define LN_INSERTHEAD(ln) do { \
-	(ln)->ln_next = llinfo_nd6.ln_next; \
-	llinfo_nd6.ln_next = (ln); \
-	(ln)->ln_prev = &llinfo_nd6; \
-	(ln)->ln_next->ln_prev = (ln); \
-	} while (/*CONSTCOND*/0)
 void
 nd6_init(void)
 {
@@ -404,18 +391,18 @@ skip1:
  * ND6 timer routine to handle ND6 entries
  */
 void
-nd6_llinfo_settimer(struct llinfo_nd6 *ln, long xtick)
+nd6_llinfo_settimer_locked(struct llentry *ln, long xtick)
 {
-	int s;
 
-	s = splsoftnet();
+	LLE_WLOCK_ASSERT(ln);
 
 	if (xtick < 0) {
 		ln->ln_expire = 0;
 		ln->ln_ntick = 0;
-		callout_stop(&ln->ln_timer_ch);
+		callout_halt(&ln->ln_timer_ch, &ln->lle_lock);
 	} else {
 		ln->ln_expire = time_uptime + xtick / hz;
+		LLE_ADDREF(ln);
 		if (xtick > INT_MAX) {
 			ln->ln_ntick = xtick - INT_MAX;
 			callout_reset(&ln->ln_timer_ch, INT_MAX,
@@ -426,8 +413,15 @@ nd6_llinfo_settimer(struct llinfo_nd6 *l
 			    nd6_llinfo_timer, ln);
 		}
 	}
+}
 
-	splx(s);
+void
+nd6_llinfo_settimer(struct llentry *ln, long xtick)
+{
+
+	LLE_WLOCK(ln);
+	nd6_llinfo_settimer_locked(ln, xtick);
+	LLE_WUNLOCK(ln);
 }
 
 /*
@@ -436,7 +430,7 @@ nd6_llinfo_settimer(struct llinfo_nd6 *l
  * Returns pointer to @src (if hold queue is not empty) or NULL.
  */
 static struct in6_addr *
-nd6_llinfo_get_holdsrc(struct llinfo_nd6 *ln, struct in6_addr *src)
+nd6_llinfo_get_holdsrc(struct llentry *ln, struct in6_addr *src)
 {
 	struct ip6_hdr *hip6;
 
@@ -459,7 +453,7 @@ nd6_llinfo_get_holdsrc(struct llinfo_nd6
 static void
 nd6_llinfo_timer(void *arg)
 {
-	struct llinfo_nd6 *ln;
+	struct llentry *ln = arg;
 	struct rtentry *rt;
 	const struct sockaddr_in6 *dst;
 	struct ifnet *ifp;
@@ -470,25 +464,43 @@ nd6_llinfo_timer(void *arg)
 	mutex_enter(softnet_lock);
 	KERNEL_LOCK(1, NULL);
 
-	ln = (struct llinfo_nd6 *)arg;
-
+	LLE_WLOCK(ln);
 	if (ln->ln_ntick > 0) {
-		nd6_llinfo_settimer(ln, ln->ln_ntick);
-		KERNEL_UNLOCK_ONE(NULL);
-		mutex_exit(softnet_lock);
-		return;
+		nd6_llinfo_settimer_locked(ln, ln->ln_ntick);
+		goto out;
 	}
 
+	if (callout_pending(&ln->la_timer)) {
+		/*
+		 * Here we are a bit odd here in the treatment of
+		 * active/pending. If the pending bit is set, it got
+		 * rescheduled before I ran. The active
+		 * bit we ignore, since if it was stopped
+		 * in ll_tablefree() and was currently running
+		 * it would have return 0 so the code would
+		 * not have deleted it since the callout could
+		 * not be stopped so we want to go through
+		 * with the delete here now. If the callout
+		 * was restarted, the pending bit will be back on and
+		 * we just want to bail since the callout_reset would
+		 * return 1 and our reference would have been removed
+		 * by nd6_llinfo_settimer_locked above since canceled
+		 * would have been 1.
+		 */
+		goto out;
+	}
+
+	ifp = ln->lle_tbl->llt_ifp;
 	rt = ln->ln_rt;
+
 	KASSERT(rt != NULL);
-	ifp = rt->rt_ifp;
 	KASSERT(ifp != NULL);
 
 	ndi = ND_IFINFO(ifp);
 	dst = satocsin6(rt_getkey(rt));
 
 	/* sanity check */
-	if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln)
+	if (rt->rt_llinfo && (struct llentry *)rt->rt_llinfo != ln)
 		panic("rt_llinfo(%p) is not equal to ln(%p)",
 		      rt->rt_llinfo, ln);
 	if (!dst)
@@ -513,7 +525,7 @@ nd6_llinfo_timer(void *arg)
 				ln->ln_hold = m0;
 				clear_llinfo_pqueue(ln);
  			}
-			(void)nd6_free(rt, 0);
+			nd6_free(rt, ln, 0);
 			ln = NULL;
 			if (m != NULL)
 				icmp6_error2(m, ICMP6_DST_UNREACH,
@@ -523,7 +535,7 @@ nd6_llinfo_timer(void *arg)
 	case ND6_LLINFO_REACHABLE:
 		if (!ND6_LLINFO_PERMANENT(ln)) {
 			ln->ln_state = ND6_LLINFO_STALE;
-			nd6_llinfo_settimer(ln, (long)nd6_gctimer * hz);
+			nd6_llinfo_settimer_locked(ln, (long)nd6_gctimer * hz);
 		}
 		break;
 
@@ -531,7 +543,7 @@ nd6_llinfo_timer(void *arg)
 	case ND6_LLINFO_STALE:
 		/* Garbage Collection(RFC 2461 5.3) */
 		if (!ND6_LLINFO_PERMANENT(ln)) {
-			(void)nd6_free(rt, 1);
+			nd6_free(rt, ln, 1);
 			ln = NULL;
 		}
 		break;
@@ -545,7 +557,7 @@ nd6_llinfo_timer(void *arg)
 			send_ns = true;
 		} else {
 			ln->ln_state = ND6_LLINFO_STALE; /* XXX */
-			nd6_llinfo_settimer(ln, (long)nd6_gctimer * hz);
+			nd6_llinfo_settimer_locked(ln, (long)nd6_gctimer * hz);
 		}
 		break;
 	case ND6_LLINFO_PROBE:
@@ -554,7 +566,7 @@ nd6_llinfo_timer(void *arg)
 			daddr6 = &dst->sin6_addr;
 			send_ns = true;
 		} else {
-			(void)nd6_free(rt, 0);
+			nd6_free(rt, ln, 0);
 			ln = NULL;
 		}
 		break;
@@ -563,11 +575,16 @@ nd6_llinfo_timer(void *arg)
 	if (send_ns) {
 		struct in6_addr src, *psrc;
 
-		nd6_llinfo_settimer(ln, (long)ndi->retrans * hz / 1000);
+		nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 		psrc = nd6_llinfo_get_holdsrc(ln, &src);
+		LLE_FREE_LOCKED(ln);
+		ln = NULL;
 		nd6_ns_output(ifp, daddr6, &dst->sin6_addr, psrc, 0);
 	}
 
+out:
+	if (ln != NULL)
+		LLE_FREE_LOCKED(ln);
 	KERNEL_UNLOCK_ONE(NULL);
 	mutex_exit(softnet_lock);
 }
@@ -786,7 +803,6 @@ nd6_accepts_rtadv(const struct nd_ifinfo
 void
 nd6_purge(struct ifnet *ifp, struct in6_ifextra *ext)
 {
-	struct llinfo_nd6 *ln, *nln;
 	struct nd_defrouter *dr, *ndr;
 	struct nd_prefix *pr, *npr;
 
@@ -862,26 +878,15 @@ nd6_purge(struct ifnet *ifp, struct in6_
 	}
 
 	/*
-	 * 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().
+	 * We may not need to nuke the neighbor cache entries here
+	 * because the neighbor cache is kept in if_afdata[AF_INET6].
+	 * nd6_purge() is invoked by in6_ifdetach() which is called
+	 * from if_detach() where everything gets purged. However
+	 * in6_ifdetach is directly called from vlan(4), so we still
+	 * need to purge entries here.
 	 */
-	ln = llinfo_nd6.ln_next;
-	while (ln != NULL && ln != &llinfo_nd6) {
-		struct rtentry *rt;
-		const 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 = satocsdl(rt->rt_gateway);
-			if (sdl->sdl_index == ifp->if_index)
-				nln = nd6_free(rt, 0);
-		}
-		ln = nln;
-	}
+	if (ext->lltable != NULL)
+		lltable_purge_entries(ext->lltable);
 }
 
 static struct rtentry *
@@ -943,8 +948,7 @@ nd6_lookup1(const struct in6_addr *addr6
 		if (rt == NULL)
 			return NULL;
 		if (rt->rt_llinfo) {
-			struct llinfo_nd6 *ln =
-			    (struct llinfo_nd6 *)rt->rt_llinfo;
+			struct llentry *ln = rt->rt_llinfo;
 			ln->ln_state = ND6_LLINFO_NOSTATE;
 		}
 	} else
@@ -1087,21 +1091,24 @@ nd6_is_addr_neighbor(const struct sockad
  * make it global, unless you have a strong reason for the change, and are sure
  * that the change is safe.
  */
-static struct llinfo_nd6 *
-nd6_free(struct rtentry *rt, int gc)
+static void
+nd6_free(struct rtentry *rt, struct llentry *ln, int gc)
 {
-	struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo, *next;
 	struct in6_addr in6 = satocsin6(rt_getkey(rt))->sin6_addr;
 	struct nd_defrouter *dr;
 	int error;
 
+	KASSERT(ln != NULL);
+	KASSERT(ln == rt->rt_llinfo);
+	LLE_WLOCK_ASSERT(ln);
+
 	/*
 	 * we used to have pfctlinput(PRC_HOSTDEAD) here.
 	 * even though it is not harmful, it was not really necessary.
 	 */
 
 	/* cancel timer */
-	nd6_llinfo_settimer(ln, -1);
+	nd6_llinfo_settimer_locked(ln, -1);
 
 	if (!ip6_forwarding) {
 		int s;
@@ -1124,12 +1131,13 @@ nd6_free(struct rtentry *rt, int gc)
 			 *      but we intentionally keep it just in case.
 			 */
 			if (dr->expire > time_uptime)
-				nd6_llinfo_settimer(ln,
+				nd6_llinfo_settimer_locked(ln,
 				    (dr->expire - time_uptime) * hz);
 			else
-				nd6_llinfo_settimer(ln, (long)nd6_gctimer * hz);
+				nd6_llinfo_settimer_locked(ln,
+				    (long)nd6_gctimer * hz);
 			splx(s);
-			return ln->ln_next;
+			return;
 		}
 
 		if (ln->ln_router || dr) {
@@ -1173,14 +1181,7 @@ nd6_free(struct rtentry *rt, int gc)
 		splx(s);
 	}
 
-	/*
-	 * Before deleting the entry, remember the next entry as the
-	 * return value.  We need this because pfxlist_onlink_check() above
-	 * might have freed other entries (particularly the old next entry) as
-	 * a side effect (XXX).
-	 */
-	next = ln->ln_next;
-
+	LLE_WUNLOCK(ln);
 	/*
 	 * Detach the route from the routing tree and the list of neighbor
 	 * caches, and disable the route entry not to be used in already
@@ -1191,8 +1192,6 @@ nd6_free(struct rtentry *rt, int gc)
 	if (error != 0) {
 		/* XXX need error message? */;
 	}
-
-	return next;
 }
 
 /*
@@ -1203,7 +1202,7 @@ nd6_free(struct rtentry *rt, int gc)
 void
 nd6_nud_hint(struct rtentry *rt)
 {
-	struct llinfo_nd6 *ln;
+	struct llentry *ln;
 
 	if (rt == NULL)
 		return;
@@ -1216,7 +1215,7 @@ nd6_nud_hint(struct rtentry *rt)
 		return;
 	}
 
-	ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+	ln = rt->rt_llinfo;
 	if (ln->ln_state < ND6_LLINFO_REACHABLE)
 		return;
 
@@ -1237,14 +1236,54 @@ nd6_nud_hint(struct rtentry *rt)
 	return;
 }
 
+static int
+nd6_purge_entry(struct lltable *llt, struct llentry *ln, void *farg)
+{
+	int *n = farg;
+
+	if (*n <= 0)
+		return 0;
+
+	if (ND6_LLINFO_PERMANENT(ln))
+		return 0;
+
+	LLE_WLOCK(ln);
+	if (ln->ln_state > ND6_LLINFO_INCOMPLETE)
+		ln->ln_state = ND6_LLINFO_STALE;
+	else
+		ln->ln_state = ND6_LLINFO_PURGE;
+	nd6_llinfo_settimer_locked(ln, 0);
+	LLE_WUNLOCK(ln);
+
+	(*n)--;
+	return 0;
+}
+
+static void
+nd6_gc_neighbors(struct lltable *llt)
+{
+	int max_gc_entries = 10;
+
+	if (ip6_neighborgcthresh >= 0 &&
+	    lltable_get_entry_count(llt) >= ip6_neighborgcthresh) {
+		/*
+		 * XXX entries that are "less recently used" should be
+		 * freed first.
+		 */
+		lltable_foreach_lle(llt, nd6_purge_entry, &max_gc_entries);
+	}
+}
+
 void
 nd6_rtrequest(int req, struct rtentry *rt, const struct rt_addrinfo *info)
 {
 	struct sockaddr *gate = rt->rt_gateway;
-	struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+	struct llentry *ln;
 	struct ifnet *ifp = rt->rt_ifp;
 	uint8_t namelen = strlen(ifp->if_xname), addrlen = ifp->if_addrlen;
 	struct ifaddr *ifa;
+	int flags = 0;
+	bool use_lo0ifp = false;
 
 	RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 
@@ -1292,6 +1331,10 @@ nd6_rtrequest(int req, struct rtentry *r
 		return;
 	}
 
+	IF_AFDATA_RLOCK(ifp);
+	ln = lla_lookup(LLTABLE6(ifp), flags, rt_getkey(rt));
+	IF_AFDATA_RUNLOCK(ifp);
+
 	if (req == RTM_RESOLVE &&
 	    (nd6_need_cache(ifp) == 0 || /* stf case */
 	     !nd6_is_addr_neighbor(satocsin6(rt_getkey(rt)), ifp))) {
@@ -1348,7 +1391,7 @@ nd6_rtrequest(int req, struct rtentry *r
 			gate = rt->rt_gateway;
 			RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 			if (ln != NULL)
-				nd6_llinfo_settimer(ln, 0);
+				nd6_llinfo_settimer_locked(ln, 0);
 			RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 			if ((rt->rt_flags & RTF_CLONING) != 0)
 				break;
@@ -1404,23 +1447,53 @@ nd6_rtrequest(int req, struct rtentry *r
 		if (ln != NULL)
 			break;	/* This happens on a route change */
 		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
+
+		/* Determine to use lo0ifp or not before lla_create */
+		ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp,
+		    &satocsin6(rt_getkey(rt))->sin6_addr);
+		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
+		if (ifa != NULL && nd6_useloopback)
+			use_lo0ifp = true;
+
 		/*
 		 * Case 2: This route may come from cloning, or a manual route
 		 * add with a LL address.
 		 */
-		R_Malloc(ln, struct llinfo_nd6 *, sizeof(*ln));
-		rt->rt_llinfo = ln;
+		flags = LLE_EXCLUSIVE;
+		if ((rt->rt_flags & RTF_CLONED) == 0)
+			flags |= LLE_IFADDR;
+
+#define	_IFP()	(use_lo0ifp ? lo0ifp : ifp)
+		IF_AFDATA_WLOCK(_IFP());
+		ln = lla_create(LLTABLE6(_IFP()), flags, rt_getkey(rt));
+		IF_AFDATA_WUNLOCK(_IFP());
+
 		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 		if (ln == NULL) {
 			log(LOG_DEBUG, "nd6_rtrequest: malloc failed\n");
 			break;
 		}
+
 		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 		nd6_inuse++;
 		nd6_allocated++;
-		memset(ln, 0, sizeof(*ln));
 		ln->ln_rt = rt;
-		callout_init(&ln->ln_timer_ch, CALLOUT_MPSAFE);
+		rt->rt_refcnt++;
+		rt->rt_llinfo = ln;
+		LLE_ADDREF(ln);
+		rt->rt_flags |= RTF_LLINFO;
+		switch (_IFP()->if_type) {
+#if NTOKEN > 0
+		case IFT_ISO88025:
+			ln->la_opaque = kmem_alloc(sizeof(struct token_rif),
+			    KM_SLEEP);
+			break;
+#endif /* NTOKEN > 0 */
+		default:
+			break;
+		}
+#undef _IFP
+
 		/* this is required for "ndp" command. - shin */
 		if (req == RTM_ADD) {
 		        /*
@@ -1436,55 +1509,17 @@ nd6_rtrequest(int req, struct rtentry *r
 			 * initialized in rtrequest(), so rt_expire is 0.
 			 */
 			ln->ln_state = ND6_LLINFO_NOSTATE;
-			nd6_llinfo_settimer(ln, 0);
+			nd6_llinfo_settimer_locked(ln, 0);
 		}
 		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
-		rt->rt_flags |= RTF_LLINFO;
-		ln->ln_next = llinfo_nd6.ln_next;
-		llinfo_nd6.ln_next = ln;
-		ln->ln_prev = &llinfo_nd6;
-		ln->ln_next->ln_prev = ln;
 
 		/*
-		 * If we have too many cache entries, initiate immediate
-		 * purging for some "less recently used" entries.  Note that
-		 * we cannot directly call nd6_free() here because it would
-		 * cause re-entering rtable related routines triggering an LOR
-		 * problem for FreeBSD.
-		 */
-		if (ip6_neighborgcthresh >= 0 &&
-		    nd6_inuse >= ip6_neighborgcthresh) {
-			int i;
-
-			for (i = 0; i < 10 && llinfo_nd6.ln_prev != ln; i++) {
-				struct llinfo_nd6 *ln_end = llinfo_nd6.ln_prev;
-
-				/* Move this entry to the head */
-				LN_DEQUEUE(ln_end);
-				LN_INSERTHEAD(ln_end);
-
-				if (ND6_LLINFO_PERMANENT(ln_end))
-					continue;
-
-				if (ln_end->ln_state > ND6_LLINFO_INCOMPLETE)
-					ln_end->ln_state = ND6_LLINFO_STALE;
-				else
-					ln_end->ln_state = ND6_LLINFO_PURGE;
-				nd6_llinfo_settimer(ln_end, 0);
-			}
-		}
-
-		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
-		/*
 		 * check if rt_getkey(rt) is an address assigned
 		 * to the interface.
 		 */
-		ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp,
-		    &satocsin6(rt_getkey(rt))->sin6_addr);
-		RT_DPRINTF("rt_getkey(rt) = %p\n", rt_getkey(rt));
 		if (ifa != NULL) {
 			const void *mac;
-			nd6_llinfo_settimer(ln, -1);
+			nd6_llinfo_settimer_locked(ln, -1);
 			ln->ln_state = ND6_LLINFO_REACHABLE;
 			ln->ln_byhint = 0;
 			if ((mac = nd6_ifptomac(ifp)) != NULL) {
@@ -1516,7 +1551,7 @@ nd6_rtrequest(int req, struct rtentry *r
 			}
 			rt->rt_flags |= RTF_LOCAL;
 		} else if (rt->rt_flags & RTF_ANNOUNCE) {
-			nd6_llinfo_settimer(ln, -1);
+			nd6_llinfo_settimer_locked(ln, -1);
 			ln->ln_state = ND6_LLINFO_REACHABLE;
 			ln->ln_byhint = 0;
 
@@ -1539,6 +1574,14 @@ nd6_rtrequest(int req, struct rtentry *r
 				}
 			}
 		}
+		LLE_WUNLOCK(ln);
+		/*
+		 * If we have too many cache entries, initiate immediate
+		 * purging for some entries.
+		 */
+		nd6_gc_neighbors(ln->lle_tbl);
+		ln = NULL;
+
 		break;
 
 	case RTM_DELETE:
@@ -1562,14 +1605,56 @@ nd6_rtrequest(int req, struct rtentry *r
 			}
 		}
 		nd6_inuse--;
-		ln->ln_next->ln_prev = ln->ln_prev;
-		ln->ln_prev->ln_next = ln->ln_next;
-		ln->ln_prev = NULL;
-		nd6_llinfo_settimer(ln, -1);
-		rt->rt_llinfo = 0;
+		rt->rt_llinfo = NULL;
 		rt->rt_flags &= ~RTF_LLINFO;
+
+		/* Have to do before IF_AFDATA_WLOCK to avoid deadlock */
+		callout_halt(&ln->la_timer, &ln->lle_lock);
+		/* XXX: LOR avoidance. We still have ref on lle. */
+		LLE_RUNLOCK(ln);
+
+		IF_AFDATA_WLOCK(ifp);
+		LLE_WLOCK(ln);
+
 		clear_llinfo_pqueue(ln);
-		Free(ln);
+		if (ln->la_opaque != NULL) {
+			switch (ifp->if_type) {
+#if NTOKEN > 0
+			case IFT_ISO88025:
+				kmem_free(ln->la_opaque,
+				    sizeof(struct token_rif));
+				break;
+#endif /* NTOKEN > 0 */
+			default:
+				break;
+			}
+		}
+
+		if (ln->la_rt != NULL) {
+			/*
+			 * Don't rtfree (may actually free objects) here.
+			 * Leave it to rtrequest1.
+			 */
+			ln->la_rt->rt_refcnt--;
+			ln->la_rt = NULL;
+		}
+		/* Guard against race with other llentry_free(). */
+		if (ln->la_flags & LLE_LINKED) {
+			LLE_REMREF(ln);
+			llentry_free(ln);
+		} else {
+			LLE_FREE_LOCKED(ln);
+		}
+
+		IF_AFDATA_WUNLOCK(ifp);
+		ln = NULL;
+	}
+
+	if (ln != NULL) {
+		if (flags & LLE_EXCLUSIVE)
+			LLE_WUNLOCK(ln);
+		else
+			LLE_RUNLOCK(ln);
 	}
 }
 
@@ -1853,7 +1938,7 @@ nd6_ioctl(u_long cmd, void *data, struct
 	}
 	case SIOCGNBRINFO_IN6:
 	{
-		struct llinfo_nd6 *ln;
+		struct llentry *ln;
 		struct in6_addr nb_addr = nbi->addr; /* make local for safety */
 		struct rtentry *rt;
 
@@ -1868,7 +1953,7 @@ nd6_ioctl(u_long cmd, void *data, struct
 			break;
 		}
 
-		ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+		ln = rt->rt_llinfo;
 		rtfree(rt);
 		if (ln == NULL) {
 			error = EINVAL;
@@ -1894,12 +1979,12 @@ nd6_ioctl(u_long cmd, void *data, struct
 }
 
 void
-nd6_llinfo_release_pkts(struct llinfo_nd6 *ln, struct ifnet *ifp,
+nd6_llinfo_release_pkts(struct llentry *ln, struct ifnet *ifp,
     struct rtentry *rt)
 {
 	struct mbuf *m_hold, *m_hold_next;
 
-	for (m_hold = ln->ln_hold, ln->ln_hold = NULL;
+	for (m_hold = ln->la_hold, ln->la_hold = NULL, ln->la_numheld = 0;
 	     m_hold != NULL;
 	     m_hold = m_hold_next) {
 		m_hold_next = m_hold->m_nextpkt;
@@ -1930,7 +2015,7 @@ nd6_cache_lladdr(
 {
 	struct nd_ifinfo *ndi = ND_IFINFO(ifp);
 	struct rtentry *rt = NULL;
-	struct llinfo_nd6 *ln = NULL;
+	struct llentry *ln = NULL;
 	int is_newentry;
 	struct sockaddr_dl *sdl = NULL;
 	int do_update;
@@ -1978,11 +2063,13 @@ nd6_cache_lladdr(
 		return;
 	if ((rt->rt_flags & (RTF_GATEWAY | RTF_LLINFO)) != RTF_LLINFO) {
 fail:
-		(void)nd6_free(rt, 0);
+		if (rt->rt_llinfo != NULL)
+			LLE_WLOCK((struct llentry *)rt->rt_llinfo);
+		nd6_free(rt, rt->rt_llinfo, 0);
 		rtfree(rt);
 		return;
 	}
-	ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+	ln = rt->rt_llinfo;
 	if (ln == NULL)
 		goto fail;
 	if (rt->rt_gateway == NULL)
@@ -2280,7 +2367,7 @@ nd6_output(struct ifnet *ifp, struct ifn
 {
 	struct mbuf *m = m0;
 	struct rtentry *rt = rt0;
-	struct llinfo_nd6 *ln = NULL;
+	struct llentry *ln = NULL;
 	int error = 0;
 
 #define RTFREE_IF_NEEDED(_rt) \
@@ -2314,7 +2401,7 @@ nd6_output(struct ifnet *ifp, struct ifn
 
 	/* Look up the neighbor cache for the nexthop */
 	if (rt != NULL && (rt->rt_flags & RTF_LLINFO) != 0)
-		ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+		ln = rt->rt_llinfo;
 	else {
 		/*
 		 * Since nd6_is_addr_neighbor() internally calls nd6_lookup(),
@@ -2325,7 +2412,7 @@ nd6_output(struct ifnet *ifp, struct ifn
 			RTFREE_IF_NEEDED(rt);
 			rt = nd6_lookup(&dst->sin6_addr, 1, ifp);
 			if (rt != NULL)
-				ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+				ln = rt->rt_llinfo;
 		}
 	}
 	if (ln == NULL || rt == NULL) {
@@ -2341,14 +2428,6 @@ nd6_output(struct ifnet *ifp, struct ifn
 		goto sendpkt;	/* send anyway */
 	}
 
-	/*
-	 * Move this entry to the head of the queue so that it is less likely
-	 * for this entry to be a target of forced garbage collection (see
-	 * nd6_rtrequest()).
-	 */
-	LN_DEQUEUE(ln);
-	LN_INSERTHEAD(ln);
-
 	/* 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) {
@@ -2540,7 +2619,7 @@ nd6_storelladdr(const struct ifnet *ifp,
 }
 
 static void 
-clear_llinfo_pqueue(struct llinfo_nd6 *ln)
+clear_llinfo_pqueue(struct llentry *ln)
 {
 	struct mbuf *m_hold, *m_hold_next;
 

Index: src/sys/netinet6/nd6.h
diff -u src/sys/netinet6/nd6.h:1.67 src/sys/netinet6/nd6.h:1.68
--- src/sys/netinet6/nd6.h:1.67	Wed Nov 18 05:16:22 2015
+++ src/sys/netinet6/nd6.h	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.h,v 1.67 2015/11/18 05:16:22 ozaki-r Exp $	*/
+/*	$NetBSD: nd6.h,v 1.68 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: nd6.h,v 1.95 2002/06/08 11:31:06 itojun Exp $	*/
 
 /*
@@ -36,21 +36,6 @@
 #include <sys/queue.h>
 #include <sys/callout.h>
 
-struct	llinfo_nd6 {
-	struct	llinfo_nd6 *ln_next;
-	struct	llinfo_nd6 *ln_prev;
-	struct	rtentry *ln_rt;
-	struct	mbuf *ln_hold;	/* last packet until resolved/timeout */
-	long	ln_asked;	/* number of queries already sent for this addr */
-	u_long	ln_expire;	/* lifetime for NDP state transition */
-	short	ln_state;	/* reachability state */
-	short	ln_router;	/* 2^0: ND6 router bit */
-	int	ln_byhint;	/* # of times we made it reachable by UL hint */
-
-	long	ln_ntick;
-	struct callout ln_timer_ch;
-};
-
 #define ND6_LLINFO_PURGE	-3
 #define ND6_LLINFO_NOSTATE	-2
 /*
@@ -361,7 +346,6 @@ extern int nd6_mmaxtries;
 extern int nd6_useloopback;
 extern int nd6_maxnudhint;
 extern int nd6_gctimer;
-extern struct llinfo_nd6 llinfo_nd6;
 extern struct nd_drhead nd_defrouter;
 extern struct nd_prhead nd_prefix;
 extern int nd6_debug;
@@ -403,6 +387,8 @@ union nd_opts {
 #define nd_opts_last		nd_opt_each.last
 #define nd_opts_done		nd_opt_each.done
 
+#include <net/if_llatbl.h>
+
 /* XXX: need nd6_var.h?? */
 /* nd6.c */
 void nd6_init(void);
@@ -414,7 +400,8 @@ struct nd_opt_hdr *nd6_option(union nd_o
 int nd6_options(union nd_opts *);
 struct	rtentry *nd6_lookup(const struct in6_addr *, int, struct ifnet *);
 void nd6_setmtu(struct ifnet *);
-void nd6_llinfo_settimer(struct llinfo_nd6 *, long);
+void nd6_llinfo_settimer(struct llentry *, long);
+void nd6_llinfo_settimer_locked(struct llentry *, long);
 void nd6_timer(void *);
 void nd6_purge(struct ifnet *, struct in6_ifextra *);
 void nd6_nud_hint(struct rtentry *);
@@ -430,7 +417,7 @@ int nd6_storelladdr(const struct ifnet *
 	const struct sockaddr *, uint8_t *, size_t);
 int nd6_sysctl(int, void *, size_t *, void *, size_t);
 int nd6_need_cache(struct ifnet *);
-void nd6_llinfo_release_pkts(struct llinfo_nd6 *, struct ifnet *,
+void nd6_llinfo_release_pkts(struct llentry *, struct ifnet *,
     struct rtentry *);
 
 /* nd6_nbr.c */

Index: src/sys/netinet6/nd6_nbr.c
diff -u src/sys/netinet6/nd6_nbr.c:1.111 src/sys/netinet6/nd6_nbr.c:1.112
--- src/sys/netinet6/nd6_nbr.c:1.111	Wed Nov 18 05:16:22 2015
+++ src/sys/netinet6/nd6_nbr.c	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6_nbr.c,v 1.111 2015/11/18 05:16:22 ozaki-r Exp $	*/
+/*	$NetBSD: nd6_nbr.c,v 1.112 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: nd6_nbr.c,v 1.61 2001/02/10 16:06:14 jinmei Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.111 2015/11/18 05:16:22 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.112 2015/11/25 06:21:26 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -548,7 +548,7 @@ nd6_na_input(struct mbuf *m, int off, in
 	char *lladdr = NULL;
 	int lladdrlen = 0;
 	struct ifaddr *ifa;
-	struct llinfo_nd6 *ln;
+	struct llentry *ln;
 	struct rtentry *rt = NULL;
 	struct sockaddr_dl *sdl;
 	union nd_opts ndopts;
@@ -652,7 +652,7 @@ nd6_na_input(struct mbuf *m, int off, in
 	 */
 	rt = nd6_lookup(&taddr6, 0, ifp);
 	if ((rt == NULL) ||
-	   ((ln = (struct llinfo_nd6 *)rt->rt_llinfo) == NULL) ||
+	   ((ln = rt->rt_llinfo) == NULL) ||
 	   ((sdl = satosdl(rt->rt_gateway)) == NULL))
 		goto freeit;
 
@@ -676,7 +676,7 @@ nd6_na_input(struct mbuf *m, int off, in
 			ln->ln_byhint = 0;
 			if (!ND6_LLINFO_PERMANENT(ln)) {
 				nd6_llinfo_settimer(ln,
-				    (long)ND_IFINFO(rt->rt_ifp)->reachable * hz);
+				    (long)ND_IFINFO(ln->lle_tbl->llt_ifp)->reachable * hz);
 			}
 		} else {
 			ln->ln_state = ND6_LLINFO_STALE;

Index: src/sys/netinet6/nd6_rtr.c
diff -u src/sys/netinet6/nd6_rtr.c:1.104 src/sys/netinet6/nd6_rtr.c:1.105
--- src/sys/netinet6/nd6_rtr.c:1.104	Mon Oct  5 04:15:42 2015
+++ src/sys/netinet6/nd6_rtr.c	Wed Nov 25 06:21:26 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6_rtr.c,v 1.104 2015/10/05 04:15:42 ozaki-r Exp $	*/
+/*	$NetBSD: nd6_rtr.c,v 1.105 2015/11/25 06:21:26 ozaki-r Exp $	*/
 /*	$KAME: nd6_rtr.c,v 1.95 2001/02/07 08:09:47 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6_rtr.c,v 1.104 2015/10/05 04:15:42 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6_rtr.c,v 1.105 2015/11/25 06:21:26 ozaki-r Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -109,12 +109,12 @@ static inline bool
 nd6_is_llinfo_probreach(struct nd_defrouter *dr)
 {
 	struct rtentry *rt = NULL;
-	struct llinfo_nd6 *ln = NULL;
+	struct llentry *ln = NULL;
 
 	rt = nd6_lookup(&dr->rtaddr, 0, dr->ifp);
 	if (rt == NULL)
 		return false;
-	ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+	ln = rt->rt_llinfo;
 	rtfree(rt);
 	if (ln == NULL || !ND6_IS_LLINFO_PROBREACH(ln))
 		return false;

Reply via email to