diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c index 80359c1..96516e7 100644 --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -715,6 +715,7 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime; /* add the prefix if not yet. */ + ND_WLOCK(); if ((pr = nd6_prefix_lookup(&pr0)) == NULL) { /* * nd6_prelist_add will install the corresponding @@ -723,6 +724,7 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) { if (carp_attached) (*carp_detach_p)(&ia->ia_ifa); + ND_WUNLOCK(); goto out; } if (pr == NULL) { @@ -731,14 +733,16 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, log(LOG_ERR, "nd6_prelist_add succeeded but " "no prefix\n"); error = EINVAL; + ND_WUNLOCK(); goto out; } } + ND_WUNLOCK(); /* relate the address to the prefix */ if (ia->ia6_ndpr == NULL) { ia->ia6_ndpr = pr; - pr->ndpr_refcnt++; + atomic_add_int(&pr->ndpr_refcnt, 1); /* * If this is the first autoconf address from the @@ -803,11 +807,25 @@ in6_control(struct socket *so, u_long cmd, caddr_t data, * as much backward compatibility as possible in terms of * the ioctl operation. * Note that in6_purgeaddr() will decrement ndpr_refcnt. + * + * We're taking a (temporary) reference here, so make sure to + * bump the refcount and only call prelist_remove() if we end + * up holding the last reference after in6_purgeaddr() returns. + * This prevents a potential leak if two threads race to delete + * the same address, and a potential double free if two threads + * race to delete distinct addresses with the same prefix. */ pr = ia->ia6_ndpr; + if (pr != NULL) + atomic_add_int(&pr->ndpr_refcnt, 1); in6_purgeaddr(&ia->ia_ifa); - if (pr && pr->ndpr_refcnt == 0) - prelist_remove(pr); + if (pr != NULL) { + ND_WLOCK(); + if (atomic_fetchadd_int(&pr->ndpr_refcnt, -1) == 1) + prelist_remove(pr); + ND_WUNLOCK(); + } + pr = NULL; EVENTHANDLER_INVOKE(ifaddr_event, ifp); break; } @@ -1561,7 +1579,7 @@ in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) "in6_unlink_ifa: autoconf'ed address " "%p has no prefix\n", ia)); } else { - ia->ia6_ndpr->ndpr_refcnt--; + atomic_add_int(&ia->ia6_ndpr->ndpr_refcnt, -1); ia->ia6_ndpr = NULL; } diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c index 85890ab..c9380f7 100644 --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -549,12 +549,12 @@ in6_ifattach_linklocal(struct ifnet *ifp, struct ifnet *altifp) * address, and then reconfigure another one, the prefix is still * valid with referring to the old link-local address. */ - if (nd6_prefix_lookup(&pr0) == NULL) { - if ((error = nd6_prelist_add(&pr0, NULL, NULL)) != 0) - return (error); - } + ND_WLOCK(); + if (nd6_prefix_lookup(&pr0) == NULL) + error = nd6_prelist_add(&pr0, NULL, NULL); + ND_WUNLOCK(); - return 0; + return (error); } /* diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c index 0e30825..02eee0f 100644 --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -116,6 +116,7 @@ static int nd6_inuse, nd6_allocated; VNET_DEFINE(struct nd_drhead, nd_defrouter); VNET_DEFINE(struct nd_prhead, nd_prefix); +VNET_DEFINE(struct rwlock, nd_rwlock); VNET_DEFINE(int, nd6_recalc_reachtm_interval) = ND6_RECALC_REACHTM_INTERVAL; #define V_nd6_recalc_reachtm_interval VNET(nd6_recalc_reachtm_interval) @@ -140,6 +141,8 @@ void nd6_init(void) { + rw_init(&V_nd_rwlock, "nd rwlock"); + LIST_INIT(&V_nd_prefix); /* initialization of the default router list */ @@ -578,10 +581,12 @@ nd6_timer(void *arg) nd6_timer, curvnet); /* expire default router list */ + ND_WLOCK(); TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, ndr) { if (dr->expire && dr->expire < time_second) defrtrlist_del(dr); } + ND_WUNLOCK(); /* * expire interface addresses. @@ -656,6 +661,7 @@ nd6_timer(void *arg) } /* expire prefix list */ + ND_WLOCK(); LIST_FOREACH_SAFE(pr, &V_nd_prefix, ndpr_entry, npr) { /* * check prefix lifetime. @@ -672,6 +678,7 @@ nd6_timer(void *arg) prelist_remove(pr); } } + ND_WUNLOCK(); CURVNET_RESTORE(); } @@ -756,6 +763,7 @@ nd6_purge(struct ifnet *ifp) struct nd_defrouter *dr, *ndr; struct nd_prefix *pr, *npr; + ND_WLOCK(); /* * Nuke default router list entries toward ifp. * We defer removal of default router list entries that is installed @@ -807,8 +815,9 @@ nd6_purge(struct ifnet *ifp) if (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) { /* Refresh default router list. */ - defrouter_select(); + defrouter_select_locked(); } + ND_WUNLOCK(); /* XXXXX * We do not nuke the neighbor cache entries here any more @@ -890,6 +899,7 @@ nd6_is_new_addr_neighbor(struct sockaddr_in6 *addr, struct ifnet *ifp) * If the address matches one of our on-link prefixes, it should be a * neighbor. */ + ND_RLOCK(); LIST_FOREACH(pr, &V_nd_prefix, ndpr_entry) { if (pr->ndpr_ifp != ifp) continue; @@ -921,9 +931,12 @@ nd6_is_new_addr_neighbor(struct sockaddr_in6 *addr, struct ifnet *ifp) } if (IN6_ARE_MASKED_ADDR_EQUAL(&pr->ndpr_prefix.sin6_addr, - &addr->sin6_addr, &pr->ndpr_mask)) + &addr->sin6_addr, &pr->ndpr_mask)) { + ND_RUNLOCK(); return (1); + } } + ND_RUNLOCK(); /* * If the address is assigned on the node of the other side of @@ -1005,6 +1018,7 @@ nd6_free(struct llentry *ln, int gc) ifp = ln->lle_tbl->llt_ifp; if (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) { + ND_WLOCK(); dr = defrouter_lookup(&L3_ADDR_SIN6(ln)->sin6_addr, ifp); if (dr != NULL && dr->expire && @@ -1028,6 +1042,7 @@ nd6_free(struct llentry *ln, int gc) nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz); + ND_WUNLOCK(); next = LIST_NEXT(ln, lle_next); LLE_REMREF(ln); LLE_WUNLOCK(ln); @@ -1076,13 +1091,14 @@ nd6_free(struct llentry *ln, int gc) * before the default router selection, we perform * the check now. */ - pfxlist_onlink_check(); + pfxlist_onlink_check_locked(); /* * Refresh default router list. */ - defrouter_select(); + defrouter_select_locked(); } + ND_WUNLOCK(); if (ln->ln_router || dr) LLE_WLOCK(ln); @@ -1221,6 +1237,7 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) * obsolete API, use sysctl under net.inet6.icmp6 */ bzero(drl, sizeof(*drl)); + ND_RLOCK(); TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) { if (i >= DRLSTSIZ) break; @@ -1233,6 +1250,7 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) drl->defrouter[i].if_index = dr->ifp->if_index; i++; } + ND_RUNLOCK(); break; case SIOCGPRLST_IN6: /* @@ -1248,6 +1266,7 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) * how about separating ioctls into two? */ bzero(oprl, sizeof(*oprl)); + ND_RLOCK(); LIST_FOREACH(pr, &V_nd_prefix, ndpr_entry) { struct nd_pfxrouter *pfr; int j; @@ -1293,6 +1312,7 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) i++; } + ND_RUNLOCK(); break; case OSIOCGIFINFO_IN6: @@ -1440,25 +1460,26 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) { /* flush all the prefix advertised by routers */ struct nd_prefix *pr, *next; + struct in6_ifaddr *ia, *ia_next; - LIST_FOREACH_SAFE(pr, &V_nd_prefix, ndpr_entry, next) { - struct in6_ifaddr *ia, *ia_next; + /* do we really have to remove addresses as well? */ + /* XXXRW: in6_ifaddrhead locking. */ + TAILQ_FOREACH_SAFE(ia, &V_in6_ifaddrhead, ia_link, ia_next) { + if ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0 || + IN6_IS_ADDR_LINKLOCAL(&ia->ia_addr.sin6_addr)) + continue; + in6_purgeaddr(&ia->ia_ifa); + } + + ND_WLOCK(); + LIST_FOREACH_SAFE(pr, &V_nd_prefix, ndpr_entry, next) { if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) continue; /* XXX */ - /* do we really have to remove addresses as well? */ - /* XXXRW: in6_ifaddrhead locking. */ - TAILQ_FOREACH_SAFE(ia, &V_in6_ifaddrhead, ia_link, - ia_next) { - if ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0) - continue; - - if (ia->ia6_ndpr == pr) - in6_purgeaddr(&ia->ia_ifa); - } prelist_remove(pr); } + ND_WUNLOCK(); break; } case SIOCSRTRFLUSH_IN6: @@ -1467,9 +1488,11 @@ nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp) struct nd_defrouter *dr, *next; defrouter_reset(); + ND_WLOCK(); TAILQ_FOREACH_SAFE(dr, &V_nd_defrouter, dr_entry, next) { defrtrlist_del(dr); } + ND_WUNLOCK(); defrouter_select(); break; } @@ -2240,9 +2263,9 @@ static int nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS); SYSCTL_DECL(_net_inet6_icmp6); #endif SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_DRLIST, nd6_drlist, - CTLFLAG_RD, nd6_sysctl_drlist, ""); + CTLFLAG_RD | CTLFLAG_MPSAFE, nd6_sysctl_drlist, ""); SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_PRLIST, nd6_prlist, - CTLFLAG_RD, nd6_sysctl_prlist, ""); + CTLFLAG_RD | CTLFLAG_MPSAFE, nd6_sysctl_prlist, ""); SYSCTL_VNET_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_MAXQLEN, nd6_maxqueuelen, CTLFLAG_RW, &VNET_NAME(nd6_maxqueuelen), 1, ""); @@ -2260,23 +2283,28 @@ nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS) d.rtaddr.sin6_family = AF_INET6; d.rtaddr.sin6_len = sizeof(d.rtaddr); - /* - * XXX locking - */ + error = sysctl_wire_old_buffer(req, 0); + if (error != 0) + return (error); + + ND_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); + goto done; d.flags = dr->flags; d.rtlifetime = dr->rtlifetime; d.expire = dr->expire; d.if_index = dr->ifp->if_index; error = SYSCTL_OUT(req, &d, sizeof(d)); if (error != 0) - return (error); + goto done; } - return (0); +done: + ND_RUNLOCK(); + + return (error); } static int @@ -2299,9 +2327,11 @@ nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS) s6.sin6_family = AF_INET6; s6.sin6_len = sizeof(s6); - /* - * XXX locking - */ + error = sysctl_wire_old_buffer(req, 0); + if (error != 0) + return (error); + + ND_RLOCK(); LIST_FOREACH(pr, &V_nd_prefix, ndpr_entry) { p.prefix = pr->ndpr_prefix; if (sa6_recoverscope(&p.prefix)) { @@ -2333,7 +2363,7 @@ nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS) p.advrtrs++; error = SYSCTL_OUT(req, &p, sizeof(p)); if (error != 0) - return (error); + goto done; LIST_FOREACH(pfr, &pr->ndpr_advrtrs, pfr_entry) { s6.sin6_addr = pfr->router->rtaddr; if (sa6_recoverscope(&s6)) @@ -2342,8 +2372,11 @@ nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS) ip6_sprintf(ip6buf, &pfr->router->rtaddr)); error = SYSCTL_OUT(req, &s6, sizeof(s6)); if (error != 0) - return (error); + goto done; } } - return (0); +done: + ND_RUNLOCK(); + + return (error); } diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h index 25d8c5d..39e24a7 100644 --- a/sys/netinet6/nd6.h +++ b/sys/netinet6/nd6.h @@ -38,6 +38,7 @@ #define RTF_ANNOUNCE RTF_PROTO2 #endif +#include #include #include @@ -341,6 +342,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 to protect access to the nd_prefix and nd_defouter lists. */ +VNET_DECLARE(struct rwlock, nd_rwlock); +#define V_nd_rwlock VNET(nd_rwlock) + +#define ND_RLOCK() rw_rlock(&V_nd_rwlock) +#define ND_RUNLOCK() rw_runlock(&V_nd_rwlock) +#define ND_WLOCK() rw_wlock(&V_nd_rwlock) +#define ND_WUNLOCK() rw_wunlock(&V_nd_rwlock) +#define ND_WOWNED() rw_wowned(&V_nd_rwlock) +#define ND_LOCK_ASSERT() rw_assert(&V_nd_rwlock, RA_LOCKED) +#define ND_RLOCK_ASSERT() rw_assert(&V_nd_rwlock, RA_RLOCKED) +#define ND_WLOCK_ASSERT() rw_assert(&V_nd_rwlock, RA_WLOCKED) + #define nd6log(x) do { if (V_nd6_debug) log x; } while (/*CONSTCOND*/ 0) VNET_DECLARE(struct callout, nd6_timer_ch); @@ -437,11 +451,13 @@ void nd6_ra_input(struct mbuf *, int, int); void prelist_del(struct nd_prefix *); void defrouter_reset(void); void defrouter_select(void); +void defrouter_select_locked(void); void defrtrlist_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); +void pfxlist_onlink_check_locked(void); struct nd_defrouter *defrouter_lookup(struct in6_addr *, struct ifnet *); struct nd_prefix *nd6_prefix_lookup(struct nd_prefixctl *); void rt6_flush(struct in6_addr *, struct ifnet *); diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c index 532c9b8..3991c8c 100644 --- a/sys/netinet6/nd6_nbr.c +++ b/sys/netinet6/nd6_nbr.c @@ -869,12 +869,7 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len) in6 = &L3_ADDR_SIN6(ln)->sin6_addr; - /* - * Lock to protect the default router list. - * XXX: this might be unnecessary, since this function - * is only called under the network software interrupt - * context. However, we keep it just for safety. - */ + ND_WLOCK(); dr = defrouter_lookup(in6, ln->lle_tbl->llt_ifp); if (dr) defrtrlist_del(dr); @@ -889,6 +884,7 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len) */ rt6_flush(&ip6->ip6_src, ifp); } + ND_WUNLOCK(); } ln->ln_router = is_router; } @@ -1185,13 +1181,15 @@ VNET_DEFINE(int, dad_init) = 0; static struct dadq * nd6_dad_find(struct ifaddr *ifa) { - struct dadq *dp; + struct dadq *dp = NULL; + ND_RLOCK(); TAILQ_FOREACH(dp, &V_dadq, dad_list) if (dp->dad_ifa == ifa) - return (dp); + break; + ND_RUNLOCK(); - return (NULL); + return (dp); } static void @@ -1271,7 +1269,9 @@ nd6_dad_start(struct ifaddr *ifa, int delay) #ifdef VIMAGE dp->dad_vnet = curvnet; #endif + ND_WLOCK(); TAILQ_INSERT_TAIL(&V_dadq, (struct dadq *)dp, dad_list); + ND_WUNLOCK(); nd6log((LOG_DEBUG, "%s: starting DAD for %s\n", if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); @@ -1314,7 +1314,9 @@ nd6_dad_stop(struct ifaddr *ifa) nd6_dad_stoptimer(dp); + ND_WLOCK(); TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list); + ND_WUNLOCK(); free(dp, M_IP6NDP); dp = NULL; ifa_free(ifa); @@ -1353,7 +1355,9 @@ nd6_dad_timer(struct dadq *dp) nd6log((LOG_INFO, "%s: could not run DAD, driver problem?\n", if_name(ifa->ifa_ifp))); + ND_WLOCK(); TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list); + ND_WUNLOCK(); free(dp, M_IP6NDP); dp = NULL; ifa_free(ifa); @@ -1406,7 +1410,9 @@ nd6_dad_timer(struct dadq *dp) if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); + ND_WLOCK(); TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list); + ND_WUNLOCK(); free(dp, M_IP6NDP); dp = NULL; ifa_free(ifa); @@ -1483,7 +1489,9 @@ nd6_dad_duplicated(struct ifaddr *ifa) } } + ND_WLOCK(); TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list); + ND_WUNLOCK(); free(dp, M_IP6NDP); dp = NULL; ifa_free(ifa); diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c index f6bae0c..34370b3 100644 --- a/sys/netinet6/nd6_rtr.c +++ b/sys/netinet6/nd6_rtr.c @@ -496,11 +496,12 @@ defrouter_addreq(struct nd_defrouter *new) return; } -struct nd_defrouter * -defrouter_lookup(struct in6_addr *addr, struct ifnet *ifp) +static struct nd_defrouter * +defrouter_lookup_locked(struct in6_addr *addr, struct ifnet *ifp) { struct nd_defrouter *dr; + ND_LOCK_ASSERT(); TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) { if (dr->ifp == ifp && IN6_ARE_ADDR_EQUAL(addr, &dr->rtaddr)) return (dr); @@ -509,6 +510,20 @@ defrouter_lookup(struct in6_addr *addr, struct ifnet *ifp) return (NULL); /* search failed */ } +__inline struct nd_defrouter * +defrouter_lookup(struct in6_addr *addr, struct ifnet *ifp) +{ + struct nd_defrouter *ret; + + if (!ND_WOWNED()) + ND_RLOCK(); + ret = defrouter_lookup_locked(addr, ifp); + if (!ND_WOWNED()) + ND_RUNLOCK(); + + return (ret); +} + /* * Remove the default route for a given router. * This is just a subroutine function for defrouter_select(), and should @@ -548,8 +563,10 @@ defrouter_reset(void) { struct nd_defrouter *dr; + ND_RLOCK(); TAILQ_FOREACH(dr, &V_nd_defrouter, dr_entry) defrouter_delreq(dr); + ND_RUNLOCK(); /* * XXX should we also nuke any default routers in the kernel, by @@ -574,6 +591,7 @@ defrtrlist_del(struct nd_defrouter *dr) deldr = dr; defrouter_delreq(dr); } + ND_WLOCK_ASSERT(); TAILQ_REMOVE(&V_nd_defrouter, dr, dr_entry); /* @@ -584,7 +602,7 @@ defrtrlist_del(struct nd_defrouter *dr) if ((pfxrtr = pfxrtr_lookup(pr, dr)) != NULL) pfxrtr_del(pfxrtr); } - pfxlist_onlink_check(); + pfxlist_onlink_check_locked(); /* * If the router is the primary one, choose a new one. @@ -592,7 +610,7 @@ defrtrlist_del(struct nd_defrouter *dr) * from the routing table. */ if (deldr) - defrouter_select(); + defrouter_select_locked(); free(dr, M_IP6NDP); } @@ -619,11 +637,13 @@ defrtrlist_del(struct nd_defrouter *dr) * complicated and the possibility of introducing bugs. */ void -defrouter_select(void) +defrouter_select_locked(void) { struct nd_defrouter *dr, *selected_dr = NULL, *installed_dr = NULL; struct llentry *ln = NULL; + ND_LOCK_ASSERT(); + /* * Let's handle easy case (3) first: * If default router list is empty, there's nothing to be done. @@ -696,6 +716,15 @@ defrouter_select(void) return; } +void +defrouter_select() +{ + + ND_RLOCK(); + defrouter_select_locked(); + ND_RUNLOCK(); +} + /* * for default router selection * regards router-preference field as a 2-bit signed integer @@ -726,8 +755,9 @@ rtpref(struct nd_defrouter *dr) static struct nd_defrouter * defrtrlist_update(struct nd_defrouter *new) { - struct nd_defrouter *dr, *n; + struct nd_defrouter *dr, *n = NULL; + ND_WLOCK(); if ((dr = defrouter_lookup(&new->rtaddr, new->ifp)) != NULL) { /* entry exists */ if (new->rtlifetime == 0) { @@ -746,8 +776,10 @@ defrtrlist_update(struct nd_defrouter *new) * 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) { + ND_WUNLOCK(); return (dr); + } /* * preferred router may be changed, so relocate @@ -762,16 +794,17 @@ defrtrlist_update(struct nd_defrouter *new) n = dr; goto insert; } + ND_WUNLOCK(); return (dr); } /* entry does not exist */ if (new->rtlifetime == 0) - return (NULL); + goto done; n = (struct nd_defrouter *)malloc(sizeof(*n), M_IP6NDP, M_NOWAIT); if (n == NULL) - return (NULL); + goto done; bzero(n, sizeof(*n)); *n = *new; @@ -793,7 +826,10 @@ insert: else TAILQ_INSERT_TAIL(&V_nd_defrouter, n, dr_entry); - defrouter_select(); + defrouter_select_locked(); + +done: + ND_WUNLOCK(); return (n); } @@ -824,7 +860,7 @@ pfxrtr_add(struct nd_prefix *pr, struct nd_defrouter *dr) LIST_INSERT_HEAD(&pr->ndpr_advrtrs, new, pfr_entry); - pfxlist_onlink_check(); + pfxlist_onlink_check_locked(); } static void @@ -839,6 +875,7 @@ nd6_prefix_lookup(struct nd_prefixctl *key) { struct nd_prefix *search; + ND_LOCK_ASSERT(); LIST_FOREACH(search, &V_nd_prefix, ndpr_entry) { if (key->ndpr_ifp == search->ndpr_ifp && key->ndpr_plen == search->ndpr_plen && @@ -886,6 +923,7 @@ nd6_prelist_add(struct nd_prefixctl *pr, struct nd_defrouter *dr, new->ndpr_prefix.sin6_addr.s6_addr32[i] &= new->ndpr_mask.s6_addr32[i]; + ND_WLOCK_ASSERT(); /* link ndpr_entry to nd_prefix list */ LIST_INSERT_HEAD(&V_nd_prefix, new, ndpr_entry); @@ -937,6 +975,7 @@ prelist_remove(struct nd_prefix *pr) if (pr->ndpr_refcnt > 0) return; /* notice here? */ + ND_WLOCK_ASSERT(); /* unlink ndpr_entry from nd_prefix list */ LIST_REMOVE(pr, ndpr_entry); @@ -946,7 +985,7 @@ prelist_remove(struct nd_prefix *pr) } free(pr, M_IP6NDP); - pfxlist_onlink_check(); + pfxlist_onlink_check_locked(); } /* @@ -979,6 +1018,7 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr, #endif } + ND_WLOCK(); if ((pr = nd6_prefix_lookup(new)) != NULL) { /* * nd6_prefix_lookup() ensures that pr and new have the same @@ -1024,10 +1064,14 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr, newprefix = 1; - if (new->ndpr_vltime == 0) + if (new->ndpr_vltime == 0) { + ND_WUNLOCK(); goto end; - if (new->ndpr_raf_onlink == 0 && new->ndpr_raf_auto == 0) + } + if (new->ndpr_raf_onlink == 0 && new->ndpr_raf_auto == 0) { + ND_WUNLOCK(); goto end; + } error = nd6_prelist_add(new, dr, &newpr); if (error != 0 || newpr == NULL) { @@ -1037,6 +1081,7 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr, ip6_sprintf(ip6buf, &new->ndpr_prefix.sin6_addr), new->ndpr_plen, if_name(new->ndpr_ifp), error, newpr)); + ND_WUNLOCK(); goto end; /* we should just give up in this case. */ } @@ -1055,6 +1100,7 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr, pr = newpr; } + ND_WUNLOCK(); /* * Address autoconfiguration based on Section 5.5.3 of RFC 2462. @@ -1246,7 +1292,7 @@ prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr, /* * note that we should use pr (not new) for reference. */ - pr->ndpr_refcnt++; + atomic_add_int(&pr->ndpr_refcnt, 1); ia6->ia6_ndpr = pr; /* @@ -1322,13 +1368,13 @@ find_pfxlist_reachable_router(struct nd_prefix *pr) * we have moved from the network but the lifetime of the prefix has not * expired yet. So we should not use the prefix if there is another prefix * that has an available router. - * But, if there is no prefix that has an available router, we still regards + * But, if there is no prefix that has an available router, we still regard * all the prefixes as on-link. This is because we can't tell if all the * routers are simply dead or if we really moved from the network and there * is no router around us. */ void -pfxlist_onlink_check() +pfxlist_onlink_check_locked() { struct nd_prefix *pr; struct in6_ifaddr *ifa; @@ -1339,6 +1385,7 @@ pfxlist_onlink_check() * Check if there is a prefix that has a reachable advertising * router. */ + ND_LOCK_ASSERT(); LIST_FOREACH(pr, &V_nd_prefix, ndpr_entry) { if (pr->ndpr_raf_onlink && find_pfxlist_reachable_router(pr)) break; @@ -1515,6 +1562,15 @@ pfxlist_onlink_check() } } +__inline void +pfxlist_onlink_check() +{ + + ND_RLOCK(); + pfxlist_onlink_check_locked(); + ND_RUNLOCK(); +} + static int nd6_prefix_onlink_rtrequest(struct nd_prefix *pr, struct ifaddr *ifa) { @@ -1618,6 +1674,7 @@ nd6_prefix_onlink(struct nd_prefix *pr) * Although such a configuration is expected to be rare, we explicitly * allow it. */ + ND_LOCK_ASSERT(); LIST_FOREACH(opr, &V_nd_prefix, ndpr_entry) { if (opr == pr) continue; @@ -1730,6 +1787,7 @@ nd6_prefix_offlink(struct nd_prefix *pr) * If there's one, try to make the prefix on-link on the * interface. */ + ND_LOCK_ASSERT(); LIST_FOREACH(opr, &V_nd_prefix, ndpr_entry) { if (opr == pr) continue; @@ -2026,7 +2084,7 @@ in6_tmpifadd(const struct in6_ifaddr *ia0, int forcegen, int delay) return (EINVAL); /* XXX */ } newia->ia6_ndpr = ia0->ia6_ndpr; - newia->ia6_ndpr->ndpr_refcnt++; + atomic_add_int(&newia->ia6_ndpr->ndpr_refcnt, 1); ifa_free(&newia->ia_ifa); /*