+ return (dr);
+}
+
+static void
+nddr_free(struct nd_defrouter *dr)
+{
+ NDDR_LOCK(dr);
+ if (dr->nddr_debug & IFD_ATTACHED) {
+ panic("%s: attached nddr %p is being freed", __func__, dr);
+ /* NOTREACHED */
+ } else if (!(dr->nddr_debug & IFD_ALLOC)) {
+ panic("%s: nddr %p cannot be freed", __func__, dr);
+ /* NOTREACHED */
+ }
+ dr->nddr_debug &= ~IFD_ALLOC;
+ NDDR_UNLOCK(dr);
+
+ lck_mtx_destroy(&dr->nddr_lock, ifa_mtx_grp);
+ zfree(nddr_zone, dr);
+}
+
+static void
+nddr_trace(struct nd_defrouter *dr, int refhold)
+{
+ struct nd_defrouter_dbg *dr_dbg = (struct nd_defrouter_dbg *)dr;
+ ctrace_t *tr;
+ uint32_t idx;
+ uint16_t *cnt;
+
+ if (!(dr->nddr_debug & IFD_DEBUG)) {
+ panic("%s: nddr %p has no debug structure", __func__, dr);
+ /* NOTREACHED */
+ }
+ if (refhold) {
+ cnt = &dr_dbg->nddr_refhold_cnt;
+ tr = dr_dbg->nddr_refhold;
+ } else {
+ cnt = &dr_dbg->nddr_refrele_cnt;
+ tr = dr_dbg->nddr_refrele;
+ }
+
+ idx = atomic_add_16_ov(cnt, 1) % NDDR_TRACE_HIST_SIZE;
+ ctrace_record(&tr[idx]);
+}
+
+void
+nddr_addref(struct nd_defrouter *nddr, int locked)
+{
+
+ if (!locked)
+ NDDR_LOCK_SPIN(nddr);
+ else
+ NDDR_LOCK_ASSERT_HELD(nddr);
+
+ if (++nddr->nddr_refcount == 0) {
+ panic("%s: nddr %p wraparound refcnt\n", __func__, nddr);
+ /* NOTREACHED */
+ } else if (nddr->nddr_trace != NULL) {
+ (*nddr->nddr_trace)(nddr, TRUE);
+ }
+
+ if (!locked)
+ NDDR_UNLOCK(nddr);
+}
+
+struct nd_defrouter *
+nddr_remref(struct nd_defrouter *nddr, int locked)
+{
+
+ if (!locked)
+ NDDR_LOCK_SPIN(nddr);
+ else
+ NDDR_LOCK_ASSERT_HELD(nddr);
+
+ if (nddr->nddr_refcount == 0) {
+ panic("%s: nddr %p negative refcnt\n", __func__, nddr);
+ /* NOTREACHED */
+ } else if (nddr->nddr_trace != NULL) {
+ (*nddr->nddr_trace)(nddr, FALSE);
+ }
+
+ if (--nddr->nddr_refcount == 0) {
+ NDDR_UNLOCK(nddr);
+ nddr_free(nddr);
+ nddr = NULL;
+ }
+
+ if (!locked && nddr != NULL)
+ NDDR_UNLOCK(nddr);
+
+ return (nddr);
+}
+
+uint64_t
+nddr_getexpire(struct nd_defrouter *dr)
+{
+ struct timeval caltime;
+ uint64_t expiry;
+
+ if (dr->expire != 0) {
+ /* account for system time change */
+ getmicrotime(&caltime);
+
+ dr->base_calendartime +=
+ NET_CALCULATE_CLOCKSKEW(caltime,
+ dr->base_calendartime, net_uptime(), dr->base_uptime);
+
+ expiry = dr->base_calendartime +
+ dr->expire - dr->base_uptime;
+ } else {
+ expiry = 0;
+ }
+ return (expiry);
+}
+
+/*
+ * Neighbor Discover Prefix structure reference counting routines.
+ */
+static struct nd_prefix *
+ndpr_alloc(int how)
+{
+ struct nd_prefix *pr;
+
+ pr = (how == M_WAITOK) ? zalloc(ndpr_zone) : zalloc_noblock(ndpr_zone);
+ if (pr != NULL) {
+ bzero(pr, ndpr_size);
+ lck_mtx_init(&pr->ndpr_lock, ifa_mtx_grp, ifa_mtx_attr);
+ RB_INIT(&pr->ndpr_prproxy_sols);
+ pr->ndpr_debug |= IFD_ALLOC;
+ if (ndpr_debug != 0) {
+ pr->ndpr_debug |= IFD_DEBUG;
+ pr->ndpr_trace = ndpr_trace;
+ }
+ }
+ return (pr);
+}
+
+static void
+ndpr_free(struct nd_prefix *pr)
+{
+ NDPR_LOCK(pr);
+ if (pr->ndpr_debug & IFD_ATTACHED) {
+ panic("%s: attached ndpr %p is being freed", __func__, pr);
+ /* NOTREACHED */
+ } else if (!(pr->ndpr_debug & IFD_ALLOC)) {
+ panic("%s: ndpr %p cannot be freed", __func__, pr);
+ /* NOTREACHED */
+ } else if (pr->ndpr_rt != NULL) {
+ panic("%s: ndpr %p route %p not freed", __func__, pr,
+ pr->ndpr_rt);
+ /* NOTREACHED */
+ } else if (pr->ndpr_prproxy_sols_cnt != 0) {
+ panic("%s: ndpr %p non-zero solicitors count (%d)",
+ __func__, pr, pr->ndpr_prproxy_sols_cnt);
+ /* NOTREACHED */
+ } else if (!RB_EMPTY(&pr->ndpr_prproxy_sols)) {
+ panic("%s: ndpr %p non-empty solicitors tree", __func__, pr);
+ /* NOTREACHED */
+ }
+ pr->ndpr_debug &= ~IFD_ALLOC;
+ NDPR_UNLOCK(pr);
+
+ lck_mtx_destroy(&pr->ndpr_lock, ifa_mtx_grp);
+ zfree(ndpr_zone, pr);
+}
+
+static void
+ndpr_trace(struct nd_prefix *pr, int refhold)
+{
+ struct nd_prefix_dbg *pr_dbg = (struct nd_prefix_dbg *)pr;
+ ctrace_t *tr;
+ u_int32_t idx;
+ u_int16_t *cnt;
+
+ if (!(pr->ndpr_debug & IFD_DEBUG)) {
+ panic("%s: ndpr %p has no debug structure", __func__, pr);
+ /* NOTREACHED */
+ }
+ if (refhold) {
+ cnt = &pr_dbg->ndpr_refhold_cnt;
+ tr = pr_dbg->ndpr_refhold;
+ } else {
+ cnt = &pr_dbg->ndpr_refrele_cnt;
+ tr = pr_dbg->ndpr_refrele;
+ }
+
+ idx = atomic_add_16_ov(cnt, 1) % NDPR_TRACE_HIST_SIZE;
+ ctrace_record(&tr[idx]);
+}
+
+void
+ndpr_addref(struct nd_prefix *ndpr, int locked)
+{
+ if (!locked)
+ NDPR_LOCK_SPIN(ndpr);
+ else
+ NDPR_LOCK_ASSERT_HELD(ndpr);
+
+ if (++ndpr->ndpr_refcount == 0) {
+ panic("%s: ndpr %p wraparound refcnt\n", __func__, ndpr);
+ /* NOTREACHED */
+ } else if (ndpr->ndpr_trace != NULL) {
+ (*ndpr->ndpr_trace)(ndpr, TRUE);
+ }
+
+ if (!locked)
+ NDPR_UNLOCK(ndpr);
+}
+
+struct nd_prefix *
+ndpr_remref(struct nd_prefix *ndpr, int locked)
+{
+ if (!locked)
+ NDPR_LOCK_SPIN(ndpr);
+ else
+ NDPR_LOCK_ASSERT_HELD(ndpr);
+
+ if (ndpr->ndpr_refcount == 0) {
+ panic("%s: ndpr %p negative refcnt\n", __func__, ndpr);
+ /* NOTREACHED */
+ } else if (ndpr->ndpr_trace != NULL) {
+ (*ndpr->ndpr_trace)(ndpr, FALSE);
+ }
+
+ if (--ndpr->ndpr_refcount == 0) {
+ if (ndpr->ndpr_addrcnt != 0) {
+ panic("%s: freeing ndpr %p with outstanding address "
+ "reference (%d)", __func__, ndpr,
+ ndpr->ndpr_addrcnt);
+ /* NOTREACHED */
+ }
+ NDPR_UNLOCK(ndpr);
+ ndpr_free(ndpr);
+ ndpr = NULL;
+ }
+
+ if (!locked && ndpr != NULL)
+ NDPR_UNLOCK(ndpr);
+
+ return (ndpr);
+}
+
+uint64_t
+ndpr_getexpire(struct nd_prefix *pr)
+{
+ struct timeval caltime;
+ uint64_t expiry;
+
+ if (pr->ndpr_expire != 0 && pr->ndpr_vltime != ND6_INFINITE_LIFETIME) {
+ /* account for system time change */
+ getmicrotime(&caltime);
+
+ pr->ndpr_base_calendartime +=
+ NET_CALCULATE_CLOCKSKEW(caltime,
+ pr->ndpr_base_calendartime, net_uptime(),
+ pr->ndpr_base_uptime);
+
+ expiry = pr->ndpr_base_calendartime +
+ pr->ndpr_expire - pr->ndpr_base_uptime;
+ } else {
+ expiry = 0;
+ }
+ return (expiry);
+}
+
+/*
+ * A supplement function used in the on-link detection below;
+ * detect if a given prefix has a (probably) reachable advertising router.
+ * XXX: lengthy function name...
+ *
+ * Callers *must* increase the reference count of nd_prefix.
+ */
+static struct nd_pfxrouter *
+find_pfxlist_reachable_router(struct nd_prefix *pr)
+{
+ struct nd_pfxrouter *pfxrtr;
+ struct rtentry *rt;
+ struct llinfo_nd6 *ln;
+ struct ifnet *ifp;
+ struct in6_addr rtaddr;
+ unsigned int genid;
+
+ lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
+ NDPR_LOCK_ASSERT_HELD(pr);
+
+ genid = pr->ndpr_genid;
+ pfxrtr = LIST_FIRST(&pr->ndpr_advrtrs);
+ while (pfxrtr) {
+ ifp = pfxrtr->router->ifp;
+ if (pfxrtr->router->stateflags & NDDRF_MAPPED)
+ rtaddr = pfxrtr->router->rtaddr_mapped;
+ else
+ rtaddr = pfxrtr->router->rtaddr;
+
+ NDPR_UNLOCK(pr);
+ lck_mtx_unlock(nd6_mutex);
+ /* Callee returns a locked route upon success */
+ if ((rt = nd6_lookup(&rtaddr, 0, ifp, 0)) != NULL) {
+ RT_LOCK_ASSERT_HELD(rt);
+ if ((ln = rt->rt_llinfo) != NULL &&
+ ND6_IS_LLINFO_PROBREACH(ln)) {
+ RT_REMREF_LOCKED(rt);
+ RT_UNLOCK(rt);
+ lck_mtx_lock(nd6_mutex);
+ NDPR_LOCK(pr);
+ break; /* found */
+ }
+ RT_REMREF_LOCKED(rt);
+ RT_UNLOCK(rt);
+ }
+ lck_mtx_lock(nd6_mutex);
+ NDPR_LOCK(pr);
+ if (pr->ndpr_genid != genid) {
+ pfxrtr = LIST_FIRST(&pr->ndpr_advrtrs);
+ genid = pr->ndpr_genid;
+ } else
+ pfxrtr = LIST_NEXT(pfxrtr, pfr_entry);
+ }
+ NDPR_LOCK_ASSERT_HELD(pr);