svn commit: r303458 - stable/10/sys/netinet6

Sean Bruno sbruno at FreeBSD.org
Thu Jul 28 20:08:02 UTC 2016


Author: sbruno
Date: Thu Jul 28 20:08:01 2016
New Revision: 303458
URL: https://svnweb.freebsd.org/changeset/base/303458

Log:
  MFC r296063 r297397 r299213
  
  296063:
  Lock the NDP default router list and count defrouter references.
  
  This addresses a number of race conditions that can cause crashes as a
  result of unsynchronized access to the list.
  
  297397
  Modify nd6_llinfo_timer() to acquire the nd6 lock before the LLE lock.
  
  When expiring a neighbour cache entry we may need to look up the associated
  default router, which requires the nd6 read lock. To avoid an LOR, the nd6
  lock should be acquired first.
  
  299213
  Clean up callers of nd6_prelist_add().
  
  nd6_prelist_add() sets *newp if and only if it is successful, so there's no
  need for code that handles the case where the return value is 0 and
  *newp == NULL. Fix some style bugs in nd6_prelist_add() while here.
  
  Submitted by:	Jason Wolfe <j at nitrology.com>

Modified:
  stable/10/sys/netinet6/in6.c
  stable/10/sys/netinet6/nd6.c
  stable/10/sys/netinet6/nd6.h
  stable/10/sys/netinet6/nd6_nbr.c
  stable/10/sys/netinet6/nd6_rtr.c
Directory Properties:
  stable/10/   (props changed)

Modified: stable/10/sys/netinet6/in6.c
==============================================================================
--- stable/10/sys/netinet6/in6.c	Thu Jul 28 19:32:25 2016	(r303457)
+++ stable/10/sys/netinet6/in6.c	Thu Jul 28 20:08:01 2016	(r303458)
@@ -732,14 +732,6 @@ in6_control(struct socket *so, u_long cm
 					(*carp_detach_p)(&ia->ia_ifa);
 				goto out;
 			}
-			if (pr == NULL) {
-				if (carp_attached)
-					(*carp_detach_p)(&ia->ia_ifa);
-				log(LOG_ERR, "nd6_prelist_add succeeded but "
-				    "no prefix\n");
-				error = EINVAL;
-				goto out;
-			}
 		}
 
 		/* relate the address to the prefix */

Modified: stable/10/sys/netinet6/nd6.c
==============================================================================
--- stable/10/sys/netinet6/nd6.c	Thu Jul 28 19:32:25 2016	(r303457)
+++ stable/10/sys/netinet6/nd6.c	Thu Jul 28 20:08:01 2016	(r303458)
@@ -114,6 +114,7 @@ VNET_DEFINE(int, nd6_debug) = 0;
 
 VNET_DEFINE(struct nd_drhead, nd_defrouter);
 VNET_DEFINE(struct nd_prhead, nd_prefix);
+VNET_DEFINE(struct rwlock, nd6_lock);
 
 VNET_DEFINE(int, nd6_recalc_reachtm_interval) = ND6_RECALC_REACHTM_INTERVAL;
 #define	V_nd6_recalc_reachtm_interval	VNET(nd6_recalc_reachtm_interval)
@@ -125,7 +126,7 @@ static int nd6_is_new_addr_neighbor(stru
 static void nd6_setmtu0(struct ifnet *, struct nd_ifinfo *);
 static void nd6_slowtimo(void *);
 static int regen_tmpaddr(struct in6_ifaddr *);
-static struct llentry *nd6_free(struct llentry *, int);
+static struct llentry *nd6_free(struct llentry **, int);
 static void nd6_llinfo_timer(void *);
 static void clear_llinfo_pqueue(struct llentry *);
 static int nd6_output_lle(struct ifnet *, struct ifnet *, struct mbuf *,
@@ -142,6 +143,8 @@ void
 nd6_init(void)
 {
 
+	rw_init(&V_nd6_lock, "nd6");
+
 	LIST_INIT(&V_nd_prefix);
 
 	/* initialization of the default router list */
@@ -162,6 +165,7 @@ nd6_destroy()
 
 	callout_drain(&V_nd6_slowtimo_ch);
 	callout_drain(&V_nd6_timer_ch);
+	rw_destroy(&V_nd6_lock);
 }
 #endif
 
@@ -466,10 +470,14 @@ nd6_llinfo_timer(void *arg)
 	struct llentry *ln;
 	struct in6_addr *dst;
 	struct ifnet *ifp;
-	struct nd_ifinfo *ndi = NULL;
+	struct nd_ifinfo *ndi;
 
 	KASSERT(arg != NULL, ("%s: arg NULL", __func__));
 	ln = (struct llentry *)arg;
+	ifp = ln->lle_tbl->llt_ifp;
+	CURVNET_SET(ifp->if_vnet);
+
+	ND6_RLOCK();
 	LLE_WLOCK(ln);
 	if (callout_pending(&ln->la_timer)) {
 		/*
@@ -489,10 +497,10 @@ nd6_llinfo_timer(void *arg)
 		 * would have been 1.
 		 */
 		LLE_WUNLOCK(ln);
+		ND6_RUNLOCK();
+		CURVNET_RESTORE();
 		return;
 	}
-	ifp = ln->lle_tbl->llt_ifp;
-	CURVNET_SET(ifp->if_vnet);
 
 	if (ln->ln_ntick > 0) {
 		if (ln->ln_ntick > INT_MAX) {
@@ -512,8 +520,7 @@ nd6_llinfo_timer(void *arg)
 	}
 
 	if (ln->la_flags & LLE_DELETED) {
-		(void)nd6_free(ln, 0);
-		ln = NULL;
+		(void)nd6_free(&ln, 0);
 		goto done;
 	}
 
@@ -539,9 +546,7 @@ nd6_llinfo_timer(void *arg)
 				ln->la_hold = m0;
 				clear_llinfo_pqueue(ln);
 			}
-			EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_TIMEDOUT);
-			(void)nd6_free(ln, 0);
-			ln = NULL;
+			(void)nd6_free(&ln, 0);
 			if (m != NULL)
 				icmp6_error2(m, ICMP6_DST_UNREACH,
 				    ICMP6_DST_UNREACH_ADDR, 0, ifp);
@@ -558,7 +563,7 @@ nd6_llinfo_timer(void *arg)
 		/* Garbage Collection(RFC 2461 5.3) */
 		if (!ND6_LLINFO_PERMANENT(ln)) {
 			EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED);
-			(void)nd6_free(ln, 1);
+			(void)nd6_free(&ln, 1);
 			ln = NULL;
 		}
 		break;
@@ -585,9 +590,7 @@ nd6_llinfo_timer(void *arg)
 			nd6_ns_output(ifp, dst, dst, ln, NULL);
 			LLE_WLOCK(ln);
 		} else {
-			EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED);
-			(void)nd6_free(ln, 0);
-			ln = NULL;
+			(void)nd6_free(&ln, 0);
 		}
 		break;
 	default:
@@ -595,8 +598,10 @@ nd6_llinfo_timer(void *arg)
 		    __func__, ln->ln_state);
 	}
 done:
-	if (ln != NULL)
+	if (ln != NULL) {
+		ND6_RUNLOCK();
 		LLE_FREE_LOCKED(ln);
+	}
 	CURVNET_RESTORE();
 }
 
@@ -608,6 +613,7 @@ void
 nd6_timer(void *arg)
 {
 	CURVNET_SET((struct vnet *) arg);
+	struct nd_drhead drq;
 	struct nd_defrouter *dr, *ndr;
 	struct nd_prefix *pr, *npr;
 	struct in6_ifaddr *ia6, *nia6;
@@ -615,10 +621,18 @@ nd6_timer(void *arg)
 	callout_reset(&V_nd6_timer_ch, V_nd6_prune * hz,
 	    nd6_timer, curvnet);
 
+	TAILQ_INIT(&drq);
+
 	/* expire default router list */
-	TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, ndr) {
+	ND6_WLOCK();
+	TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, ndr)
 		if (dr->expire && dr->expire < time_uptime)
-			defrtrlist_del(dr);
+			defrouter_unlink(dr, &drq);
+	ND6_WUNLOCK();
+
+	while ((dr = TAILQ_FIRST(&drq)) != NULL) {
+		TAILQ_REMOVE(&drq, dr, dr_entry);
+		defrouter_del(dr);
 	}
 
 	/*
@@ -813,29 +827,37 @@ regen_tmpaddr(struct in6_ifaddr *ia6)
 void
 nd6_purge(struct ifnet *ifp)
 {
+	struct nd_drhead drq;
 	struct nd_defrouter *dr, *ndr;
 	struct nd_prefix *pr, *npr;
 
+	TAILQ_INIT(&drq);
+
 	/*
 	 * Nuke default router list entries toward ifp.
 	 * We defer removal of default router list entries that is installed
 	 * in the routing table, in order to keep additional side effects as
 	 * small as possible.
 	 */
+	ND6_WLOCK();
 	TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, ndr) {
 		if (dr->installed)
 			continue;
-
 		if (dr->ifp == ifp)
-			defrtrlist_del(dr);
+			defrouter_unlink(dr, &drq);
 	}
 
 	TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, ndr) {
 		if (!dr->installed)
 			continue;
-
 		if (dr->ifp == ifp)
-			defrtrlist_del(dr);
+			defrouter_unlink(dr, &drq);
+	}
+	ND6_WUNLOCK();
+
+	while ((dr = TAILQ_FIRST(&drq)) != NULL) {
+		TAILQ_REMOVE(&drq, dr, dr_entry);
+		defrouter_del(dr);
 	}
 
 	/* Nuke prefix list entries toward ifp */
@@ -1046,13 +1068,27 @@ nd6_is_addr_neighbor(struct sockaddr_in6
  * that the change is safe.
  */
 static struct llentry *
-nd6_free(struct llentry *ln, int gc)
+nd6_free(struct llentry **lnp, int gc)
 {
-        struct llentry *next;
-	struct nd_defrouter *dr;
 	struct ifnet *ifp;
+	struct llentry *ln, *next;
+	struct nd_defrouter *dr;
+
+	ln = *lnp;
+	*lnp = NULL;
 
 	LLE_WLOCK_ASSERT(ln);
+	ND6_RLOCK_ASSERT();
+
+	ifp = ln->lle_tbl->llt_ifp;
+	if ((ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) != 0)
+		dr = defrouter_lookup_locked(&L3_ADDR_SIN6(ln)->sin6_addr, ifp);
+	else
+		dr = NULL;
+	ND6_RUNLOCK();
+
+	if ((ln->la_flags & LLE_DELETED) == 0)
+		EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED);
 
 	/*
 	 * we used to have pfctlinput(PRC_HOSTDEAD) here.
@@ -1062,11 +1098,7 @@ nd6_free(struct llentry *ln, int gc)
 	/* cancel timer */
 	nd6_llinfo_settimer_locked(ln, -1);
 
-	ifp = ln->lle_tbl->llt_ifp;
-
 	if (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) {
-		dr = defrouter_lookup(&L3_ADDR_SIN6(ln)->sin6_addr, ifp);
-
 		if (dr != NULL && dr->expire &&
 		    ln->ln_state == ND6_LLINFO_STALE && gc) {
 			/*
@@ -1091,6 +1123,7 @@ nd6_free(struct llentry *ln, int gc)
 			next = LIST_NEXT(ln, lle_next);
 			LLE_REMREF(ln);
 			LLE_WUNLOCK(ln);
+			defrouter_rele(dr);
 			return (next);
 		}
 
@@ -1173,6 +1206,8 @@ nd6_free(struct llentry *ln, int gc)
 
 	IF_AFDATA_UNLOCK(ifp);
 
+	if (dr != NULL)
+		defrouter_rele(dr);
 	return (next);
 }
 
@@ -1251,12 +1286,13 @@ nd6_rtrequest(int req, struct rtentry *r
 		/*
 		 * check for default route
 		 */
-		if (IN6_ARE_ADDR_EQUAL(&in6addr_any, 
-				       &SIN6(rt_key(rt))->sin6_addr)) {
-
+		if (IN6_ARE_ADDR_EQUAL(&in6addr_any,
+		    &SIN6(rt_key(rt))->sin6_addr)) {
 			dr = defrouter_lookup(&gateway->sin6_addr, ifp);
-			if (dr != NULL)
+			if (dr != NULL) {
 				dr->installed = 0;
+				defrouter_rele(dr);
+			}
 		}
 		break;
 	}
@@ -1444,12 +1480,22 @@ nd6_ioctl(u_long cmd, caddr_t data, stru
 	case SIOCSRTRFLUSH_IN6:
 	{
 		/* flush all the default routers */
-		struct nd_defrouter *dr, *next;
+		struct nd_drhead drq;
+		struct nd_defrouter *dr;
+
+		TAILQ_INIT(&drq);
 
 		defrouter_reset();
-		TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, next) {
-			defrtrlist_del(dr);
+
+		ND6_WLOCK();
+		while ((dr = TAILQ_FIRST(&V_nd_defrouter)) != NULL)
+			defrouter_unlink(dr, &drq);
+		ND6_WUNLOCK();
+		while ((dr = TAILQ_FIRST(&drq)) != NULL) {
+			TAILQ_REMOVE(&drq, dr, dr_entry);
+			defrouter_del(dr);
 		}
+
 		defrouter_select();
 		break;
 	}
@@ -2200,30 +2246,33 @@ nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS)
 	struct nd_defrouter *dr;
 	int error;
 
-	if (req->newptr)
+	if (req->newptr != NULL)
 		return (EPERM);
 
+	error = sysctl_wire_old_buffer(req, 0);
+	if (error != 0)
+		return (error);
+
 	bzero(&d, sizeof(d));
 	d.rtaddr.sin6_family = AF_INET6;
 	d.rtaddr.sin6_len = sizeof(d.rtaddr);
 
-	/*
-	 * XXX locking
-	 */
+	ND6_RLOCK();
 	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) {
 		d.rtaddr.sin6_addr = dr->rtaddr;
 		error = sa6_recoverscope(&d.rtaddr);
 		if (error != 0)
-			return (error);
+			break;
 		d.flags = dr->raflags;
 		d.rtlifetime = dr->rtlifetime;
 		d.expire = dr->expire + (time_second - time_uptime);
 		d.if_index = dr->ifp->if_index;
 		error = SYSCTL_OUT(req, &d, sizeof(d));
 		if (error != 0)
-			return (error);
+			break;
 	}
-	return (0);
+	ND6_RUNLOCK();
+	return (error);
 }
 
 static int

Modified: stable/10/sys/netinet6/nd6.h
==============================================================================
--- stable/10/sys/netinet6/nd6.h	Thu Jul 28 19:32:25 2016	(r303457)
+++ stable/10/sys/netinet6/nd6.h	Thu Jul 28 20:08:01 2016	(r303458)
@@ -243,6 +243,7 @@ struct nd_defrouter {
 	u_long	expire;
 	struct ifnet *ifp;
 	int	installed;	/* is installed into kernel routing table */
+	u_int	refcnt;
 };
 
 struct nd_prefixctl {
@@ -342,6 +343,19 @@ VNET_DECLARE(int, nd6_onlink_ns_rfc4861)
 #define	V_nd6_debug			VNET(nd6_debug)
 #define	V_nd6_onlink_ns_rfc4861		VNET(nd6_onlink_ns_rfc4861)
 
+/* Lock for the prefix and default router lists. */
+VNET_DECLARE(struct rwlock, nd6_lock);
+#define	V_nd6_lock			VNET(nd6_lock)
+
+#define	ND6_RLOCK()			rw_rlock(&V_nd6_lock)
+#define	ND6_RUNLOCK()			rw_runlock(&V_nd6_lock)
+#define	ND6_WLOCK()			rw_wlock(&V_nd6_lock)
+#define	ND6_WUNLOCK()			rw_wunlock(&V_nd6_lock)
+#define	ND6_WLOCK_ASSERT()		rw_assert(&V_nd6_lock, RA_WLOCKED)
+#define	ND6_RLOCK_ASSERT()		rw_assert(&V_nd6_lock, RA_RLOCKED)
+#define	ND6_LOCK_ASSERT()		rw_assert(&V_nd6_lock, RA_LOCKED)
+#define	ND6_UNLOCK_ASSERT()		rw_assert(&V_nd6_lock, RA_UNLOCKED)
+
 #define nd6log(x)	do { if (V_nd6_debug) log x; } while (/*CONSTCOND*/ 0)
 
 VNET_DECLARE(struct callout, nd6_timer_ch);
@@ -447,12 +461,17 @@ void nd6_rs_input(struct mbuf *, int, in
 void nd6_ra_input(struct mbuf *, int, int);
 void defrouter_reset(void);
 void defrouter_select(void);
-void defrtrlist_del(struct nd_defrouter *);
+void defrouter_ref(struct nd_defrouter *);
+void defrouter_rele(struct nd_defrouter *);
+bool defrouter_remove(struct in6_addr *, struct ifnet *);
+void defrouter_unlink(struct nd_defrouter *, struct nd_drhead *);
+void defrouter_del(struct nd_defrouter *);
 void prelist_remove(struct nd_prefix *);
 int nd6_prelist_add(struct nd_prefixctl *, struct nd_defrouter *,
 	struct nd_prefix **);
 void pfxlist_onlink_check(void);
 struct nd_defrouter *defrouter_lookup(struct in6_addr *, struct ifnet *);
+struct nd_defrouter *defrouter_lookup_locked(struct in6_addr *, struct ifnet *);
 struct nd_prefix *nd6_prefix_lookup(struct nd_prefixctl *);
 void rt6_flush(struct in6_addr *, struct ifnet *);
 int nd6_setdefaultiface(int);

Modified: stable/10/sys/netinet6/nd6_nbr.c
==============================================================================
--- stable/10/sys/netinet6/nd6_nbr.c	Thu Jul 28 19:32:25 2016	(r303457)
+++ stable/10/sys/netinet6/nd6_nbr.c	Thu Jul 28 20:08:01 2016	(r303458)
@@ -895,25 +895,17 @@ nd6_na_input(struct mbuf *m, int off, in
 			 * Remove the sender from the Default Router List and
 			 * update the Destination Cache entries.
 			 */
-			struct nd_defrouter *dr;
-			struct in6_addr *in6;
-
-			in6 = &L3_ADDR_SIN6(ln)->sin6_addr;
-
-			dr = defrouter_lookup(in6, ln->lle_tbl->llt_ifp);
-			if (dr)
-				defrtrlist_del(dr);
-			else if (ND_IFINFO(ln->lle_tbl->llt_ifp)->flags &
-			    ND6_IFF_ACCEPT_RTADV) {
+			if (!defrouter_remove(&L3_ADDR_SIN6(ln)->sin6_addr,
+			    ln->lle_tbl->llt_ifp) &&
+			    (ND_IFINFO(ln->lle_tbl->llt_ifp)->flags &
+			     ND6_IFF_ACCEPT_RTADV) != 0)
 				/*
 				 * Even if the neighbor is not in the default
-				 * router list, the neighbor may be used
-				 * as a next hop for some destinations
-				 * (e.g. redirect case). So we must
-				 * call rt6_flush explicitly.
+				 * router list, the neighbor may be used as a
+				 * next hop for some destinations (e.g. redirect
+				 * case). So we must call rt6_flush explicitly.
 				 */
 				rt6_flush(&ip6->ip6_src, ifp);
-			}
 		}
 		ln->ln_router = is_router;
 	}

Modified: stable/10/sys/netinet6/nd6_rtr.c
==============================================================================
--- stable/10/sys/netinet6/nd6_rtr.c	Thu Jul 28 19:32:25 2016	(r303457)
+++ stable/10/sys/netinet6/nd6_rtr.c	Thu Jul 28 20:08:01 2016	(r303458)
@@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/systm.h>
 #include <sys/malloc.h>
 #include <sys/mbuf.h>
+#include <sys/refcount.h>
 #include <sys/socket.h>
 #include <sys/sockio.h>
 #include <sys/time.h>
@@ -218,6 +219,8 @@ nd6_ra_input(struct mbuf *m, int off, in
 	struct nd_defrouter *dr;
 	char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
 
+	dr = NULL;
+
 	/*
 	 * We only accept RAs only when the per-interface flag
 	 * ND6_IFF_ACCEPT_RTADV is on the receiving interface.
@@ -367,6 +370,10 @@ nd6_ra_input(struct mbuf *m, int off, in
 			(void)prelist_update(&pr, dr, m, mcast);
 		}
 	}
+	if (dr != NULL) {
+		defrouter_rele(dr);
+		dr = NULL;
+	}
 
 	/*
 	 * MTU
@@ -444,10 +451,6 @@ nd6_ra_input(struct mbuf *m, int off, in
 	m_freem(m);
 }
 
-/*
- * default router list proccessing sub routines
- */
-
 /* tell the change to user processes watching the routing socket. */
 static void
 nd6_rtmsg(int cmd, struct rtentry *rt)
@@ -476,6 +479,10 @@ nd6_rtmsg(int cmd, struct rtentry *rt)
 		ifa_free(ifa);
 }
 
+/*
+ * default router list proccessing sub routines
+ */
+
 static void
 defrouter_addreq(struct nd_defrouter *new)
 {
@@ -504,16 +511,43 @@ defrouter_addreq(struct nd_defrouter *ne
 }
 
 struct nd_defrouter *
-defrouter_lookup(struct in6_addr *addr, struct ifnet *ifp)
+defrouter_lookup_locked(struct in6_addr *addr, struct ifnet *ifp)
 {
 	struct nd_defrouter *dr;
 
-	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) {
-		if (dr->ifp == ifp && IN6_ARE_ADDR_EQUAL(addr, &dr->rtaddr))
+	ND6_LOCK_ASSERT();
+	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry)
+		if (dr->ifp == ifp && IN6_ARE_ADDR_EQUAL(addr, &dr->rtaddr)) {
+			defrouter_ref(dr);
 			return (dr);
-	}
+		}
+	return (NULL);
+}
+
+struct nd_defrouter *
+defrouter_lookup(struct in6_addr *addr, struct ifnet *ifp)
+{
+	struct nd_defrouter *dr;
 
-	return (NULL);		/* search failed */
+	ND6_RLOCK();
+	dr = defrouter_lookup_locked(addr, ifp);
+	ND6_RUNLOCK();
+	return (dr);
+}
+
+void
+defrouter_ref(struct nd_defrouter *dr)
+{
+
+	refcount_acquire(&dr->refcnt);
+}
+
+void
+defrouter_rele(struct nd_defrouter *dr)
+{
+
+	if (refcount_release(&dr->refcnt))
+		free(dr, M_IP6NDP);
 }
 
 /*
@@ -548,15 +582,41 @@ defrouter_delreq(struct nd_defrouter *dr
 }
 
 /*
- * remove all default routes from default router list
+ * Remove all default routes from default router list.
  */
 void
 defrouter_reset(void)
 {
-	struct nd_defrouter *dr;
+	struct nd_defrouter *dr, **dra;
+	int count, i;
+
+	count = i = 0;
 
+	/*
+	 * We can't delete routes with the ND lock held, so make a copy of the
+	 * current default router list and use that when deleting routes.
+	 */
+	ND6_RLOCK();
 	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry)
-		defrouter_delreq(dr);
+		count++;
+	ND6_RUNLOCK();
+
+	dra = malloc(count * sizeof(*dra), M_TEMP, M_WAITOK | M_ZERO);
+
+	ND6_RLOCK();
+	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) {
+		if (i == count)
+			break;
+		defrouter_ref(dr);
+		dra[i++] = dr;
+	}
+	ND6_RUNLOCK();
+
+	for (i = 0; i < count && dra[i] != NULL; i++) {
+		defrouter_delreq(dra[i]);
+		defrouter_rele(dra[i]);
+	}
+	free(dra, M_TEMP);
 
 	/*
 	 * XXX should we also nuke any default routers in the kernel, by
@@ -564,12 +624,53 @@ defrouter_reset(void)
 	 */
 }
 
+/*
+ * Look up a matching default router list entry and remove it. Returns true if a
+ * matching entry was found, false otherwise.
+ */
+bool
+defrouter_remove(struct in6_addr *addr, struct ifnet *ifp)
+{
+	struct nd_defrouter *dr;
+
+	ND6_WLOCK();
+	dr = defrouter_lookup_locked(addr, ifp);
+	if (dr == NULL) {
+		ND6_WUNLOCK();
+		return (false);
+	}
+
+	defrouter_unlink(dr, NULL);
+	ND6_WUNLOCK();
+	defrouter_del(dr);
+	defrouter_rele(dr);
+	return (true);
+}
+
+/*
+ * Remove a router from the global list and optionally stash it in a
+ * caller-supplied queue.
+ *
+ * The ND lock must be held.
+ */
 void
-defrtrlist_del(struct nd_defrouter *dr)
+defrouter_unlink(struct nd_defrouter *dr, struct nd_drhead *drq)
+{
+
+	ND6_WLOCK_ASSERT();
+	TAILQ_REMOVE(&V_nd_defrouter, dr, dr_entry);
+	if (drq != NULL)
+		TAILQ_INSERT_TAIL(drq, dr, dr_entry);
+}
+
+void
+defrouter_del(struct nd_defrouter *dr)
 {
 	struct nd_defrouter *deldr = NULL;
 	struct nd_prefix *pr;
 
+	ND6_UNLOCK_ASSERT();
+
 	/*
 	 * Flush all the routing table entries that use the router
 	 * as a next hop.
@@ -581,7 +682,6 @@ defrtrlist_del(struct nd_defrouter *dr)
 		deldr = dr;
 		defrouter_delreq(dr);
 	}
-	TAILQ_REMOVE(&V_nd_defrouter, dr, dr_entry);
 
 	/*
 	 * Also delete all the pointers to the router in each prefix lists.
@@ -601,7 +701,10 @@ defrtrlist_del(struct nd_defrouter *dr)
 	if (deldr)
 		defrouter_select();
 
-	free(dr, M_IP6NDP);
+	/*
+	 * Release the list reference.
+	 */
+	defrouter_rele(dr);
 }
 
 /*
@@ -628,27 +731,32 @@ defrtrlist_del(struct nd_defrouter *dr)
 void
 defrouter_select(void)
 {
-	struct nd_defrouter *dr, *selected_dr = NULL, *installed_dr = NULL;
+	struct nd_defrouter *dr, *selected_dr, *installed_dr;
 	struct llentry *ln = NULL;
 
+	ND6_RLOCK();
 	/*
 	 * Let's handle easy case (3) first:
 	 * If default router list is empty, there's nothing to be done.
 	 */
-	if (TAILQ_EMPTY(&V_nd_defrouter))
+	if (TAILQ_EMPTY(&V_nd_defrouter)) {
+		ND6_RUNLOCK();
 		return;
+	}
 
 	/*
 	 * Search for a (probably) reachable router from the list.
 	 * We just pick up the first reachable one (if any), assuming that
 	 * the ordering rule of the list described in defrtrlist_update().
 	 */
+	selected_dr = installed_dr = NULL;
 	TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) {
 		IF_AFDATA_RLOCK(dr->ifp);
 		if (selected_dr == NULL &&
 		    (ln = nd6_lookup(&dr->rtaddr, 0, dr->ifp)) &&
 		    ND6_IS_LLINFO_PROBREACH(ln)) {
 			selected_dr = dr;
+			defrouter_ref(selected_dr);
 		}
 		IF_AFDATA_RUNLOCK(dr->ifp);
 		if (ln != NULL) {
@@ -656,12 +764,15 @@ defrouter_select(void)
 			ln = NULL;
 		}
 
-		if (dr->installed && installed_dr == NULL)
-			installed_dr = dr;
-		else if (dr->installed && installed_dr) {
-			/* this should not happen.  warn for diagnosis. */
-			log(LOG_ERR, "defrouter_select: more than one router"
-			    " is installed\n");
+		if (dr->installed) {
+			if (installed_dr == NULL) {
+				installed_dr = dr;
+				defrouter_ref(installed_dr);
+			} else {
+				/* this should not happen.  warn for diagnosis. */
+				log(LOG_ERR,
+		    "defrouter_select: more than one router is installed\n");
+			}
 		}
 	}
 	/*
@@ -673,21 +784,25 @@ defrouter_select(void)
 	 * or when the new one has a really higher preference value.
 	 */
 	if (selected_dr == NULL) {
-		if (installed_dr == NULL || !TAILQ_NEXT(installed_dr, dr_entry))
+		if (installed_dr == NULL ||
+		    TAILQ_NEXT(installed_dr, dr_entry) == NULL)
 			selected_dr = TAILQ_FIRST(&V_nd_defrouter);
 		else
 			selected_dr = TAILQ_NEXT(installed_dr, dr_entry);
-	} else if (installed_dr) {
+		defrouter_ref(selected_dr);
+	} else if (installed_dr != NULL) {
 		IF_AFDATA_RLOCK(installed_dr->ifp);
 		if ((ln = nd6_lookup(&installed_dr->rtaddr, 0, installed_dr->ifp)) &&
 		    ND6_IS_LLINFO_PROBREACH(ln) &&
 		    rtpref(selected_dr) <= rtpref(installed_dr)) {
+			defrouter_rele(selected_dr);
 			selected_dr = installed_dr;
 		}
 		IF_AFDATA_RUNLOCK(installed_dr->ifp);
 		if (ln != NULL)
 			LLE_RUNLOCK(ln);
 	}
+	ND6_RUNLOCK();
 
 	/*
 	 * If the selected router is different than the installed one,
@@ -695,10 +810,13 @@ defrouter_select(void)
 	 * Note that the selected router is never NULL here.
 	 */
 	if (installed_dr != selected_dr) {
-		if (installed_dr)
+		if (installed_dr != NULL) {
 			defrouter_delreq(installed_dr);
+			defrouter_rele(installed_dr);
+		}
 		defrouter_addreq(selected_dr);
 	}
+	defrouter_rele(selected_dr);
 }
 
 /*
@@ -734,13 +852,14 @@ defrtrlist_update(struct nd_defrouter *n
 	struct nd_defrouter *dr, *n;
 	int oldpref;
 
-	if ((dr = defrouter_lookup(&new->rtaddr, new->ifp)) != NULL) {
-		/* entry exists */
-		if (new->rtlifetime == 0) {
-			defrtrlist_del(dr);
-			return (NULL);
-		}
+	if (new->rtlifetime == 0) {
+		defrouter_remove(&new->rtaddr, new->ifp);
+		return (NULL);
+	}
 
+	ND6_WLOCK();
+	dr = defrouter_lookup_locked(&new->rtaddr, new->ifp);
+	if (dr != NULL) {
 		oldpref = rtpref(dr);
 
 		/* override */
@@ -753,8 +872,10 @@ defrtrlist_update(struct nd_defrouter *n
 		 * to sort the entries. Also make sure the selected
 		 * router is still installed in the kernel.
 		 */
-		if (dr->installed && rtpref(new) == oldpref)
+		if (dr->installed && rtpref(new) == oldpref) {
+			ND6_WUNLOCK();
 			return (dr);
+		}
 
 		/*
 		 * The preferred router may have changed, so relocate this
@@ -762,19 +883,17 @@ defrtrlist_update(struct nd_defrouter *n
 		 */
 		TAILQ_REMOVE(&V_nd_defrouter, dr, dr_entry);
 		n = dr;
-		goto insert;
+	} else {
+		n = malloc(sizeof(*n), M_IP6NDP, M_NOWAIT | M_ZERO);
+		if (n == NULL) {
+			ND6_WUNLOCK();
+			return (NULL);
+		}
+		memcpy(n, new, sizeof(*n));
+		/* Initialize with an extra reference for the caller. */
+		refcount_init(&n->refcnt, 2);
 	}
 
-	/* entry does not exist */
-	if (new->rtlifetime == 0)
-		return (NULL);
-
-	n = malloc(sizeof(*n), M_IP6NDP, M_NOWAIT | M_ZERO);
-	if (n == NULL)
-		return (NULL);
-	memcpy(n, new, sizeof(*n));
-
-insert:
 	/*
 	 * Insert the new router in the Default Router List;
 	 * The Default Router List should be in the descending order
@@ -787,10 +906,11 @@ insert:
 		if (rtpref(n) > rtpref(dr))
 			break;
 	}
-	if (dr)
+	if (dr != NULL)
 		TAILQ_INSERT_BEFORE(dr, n, dr_entry);
 	else
 		TAILQ_INSERT_TAIL(&V_nd_defrouter, n, dr_entry);
+	ND6_WUNLOCK();
 
 	defrouter_select();
 
@@ -819,6 +939,7 @@ pfxrtr_add(struct nd_prefix *pr, struct 
 	if (new == NULL)
 		return;
 	new->router = dr;
+	defrouter_ref(dr);
 
 	LIST_INSERT_HEAD(&pr->ndpr_advrtrs, new, pfr_entry);
 
@@ -828,7 +949,9 @@ pfxrtr_add(struct nd_prefix *pr, struct 
 static void
 pfxrtr_del(struct nd_pfxrouter *pfr)
 {
+
 	LIST_REMOVE(pfr, pfr_entry);
+	defrouter_rele(pfr->router);
 	free(pfr, M_IP6NDP);
 }
 
@@ -869,11 +992,9 @@ nd6_prelist_add(struct nd_prefixctl *pr,
 	new->ndpr_flags = pr->ndpr_flags;
 	if ((error = in6_init_prefix_ltimes(new)) != 0) {
 		free(new, M_IP6NDP);
-		return(error);
+		return (error);
 	}
 	new->ndpr_lastupdate = time_uptime;
-	if (newp != NULL)
-		*newp = new;
 
 	/* initialization */
 	LIST_INIT(&new->ndpr_advrtrs);
@@ -899,10 +1020,11 @@ nd6_prelist_add(struct nd_prefixctl *pr,
 		}
 	}
 
-	if (dr)
+	if (dr != NULL)
 		pfxrtr_add(new, dr);
-
-	return 0;
+	if (newp != NULL)
+		*newp = new;
+	return (0);
 }
 
 void
@@ -959,7 +1081,6 @@ prelist_update(struct nd_prefixctl *new,
 	struct ifnet *ifp = new->ndpr_ifp;
 	struct nd_prefix *pr;
 	int error = 0;
-	int newprefix = 0;
 	int auth;
 	struct in6_addrlifetime lt6_tmp;
 	char ip6buf[INET6_ADDRSTRLEN];
@@ -1017,23 +1138,17 @@ prelist_update(struct nd_prefixctl *new,
 		if (dr && pfxrtr_lookup(pr, dr) == NULL)
 			pfxrtr_add(pr, dr);
 	} else {
-		struct nd_prefix *newpr = NULL;
-
-		newprefix = 1;
-
 		if (new->ndpr_vltime == 0)
 			goto end;
 		if (new->ndpr_raf_onlink == 0 && new->ndpr_raf_auto == 0)
 			goto end;
 
-		error = nd6_prelist_add(new, dr, &newpr);
-		if (error != 0 || newpr == NULL) {
+		error = nd6_prelist_add(new, dr, &pr);
+		if (error != 0) {
 			nd6log((LOG_NOTICE, "prelist_update: "
-			    "nd6_prelist_add failed for %s/%d on %s "
-			    "errno=%d, returnpr=%p\n",
+			    "nd6_prelist_add failed for %s/%d on %s errno=%d\n",
 			    ip6_sprintf(ip6buf, &new->ndpr_prefix.sin6_addr),
-			    new->ndpr_plen, if_name(new->ndpr_ifp),
-			    error, newpr));
+			    new->ndpr_plen, if_name(new->ndpr_ifp), error));
 			goto end; /* we should just give up in this case. */
 		}
 
@@ -1044,13 +1159,11 @@ prelist_update(struct nd_prefixctl *new,
 		 * addresses.  Thus, we explicitly make sure that the prefix
 		 * itself expires now.
 		 */
-		if (newpr->ndpr_raf_onlink == 0) {
-			newpr->ndpr_vltime = 0;
-			newpr->ndpr_pltime = 0;
-			in6_init_prefix_ltimes(newpr);
+		if (pr->ndpr_raf_onlink == 0) {
+			pr->ndpr_vltime = 0;
+			pr->ndpr_pltime = 0;
+			in6_init_prefix_ltimes(pr);
 		}
-
-		pr = newpr;
 	}
 
 	/*
@@ -1346,6 +1459,7 @@ pfxlist_onlink_check()
 	 * that does not advertise any prefixes.
 	 */
 	if (pr == NULL) {
+		ND6_RLOCK();
 		TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) {
 			struct nd_prefix *pr0;
 
@@ -1356,6 +1470,7 @@ pfxlist_onlink_check()
 			if (pfxrtr != NULL)
 				break;
 		}
+		ND6_RUNLOCK();
 	}
 	if (pr != NULL || (!TAILQ_EMPTY(&V_nd_defrouter) && pfxrtr == NULL)) {
 		/*


More information about the svn-src-all mailing list