+ /* If none was selected, use the currently installed one */
+ if (ip6_doscopedroute && selected_dr == NULL && installed_dr != NULL) {
+ selected_dr = installed_dr;
+ NDDR_ADDREF(selected_dr);
+ }
+
+ /*
+ * Install the unreachable one(s) if necesssary.
+ */
+ for (dr = TAILQ_FIRST(&nd_defrouter); dr;
+ dr = TAILQ_NEXT(dr, dr_entry)) {
+ struct nd_defrouter *_dr;
+
+ if (!ip6_doscopedroute)
+ break;
+
+ NDDR_LOCK(dr);
+
+ /* If already (or will be) installed, skip */
+ if ((dr->stateflags & NDDRF_INSTALLED) || dr->genid == -1) {
+ NDDR_UNLOCK(dr);
+ continue;
+ }
+
+ /* See if there is already a default router for the link */
+ for (_dr = TAILQ_FIRST(&nd_defrouter); _dr;
+ _dr = TAILQ_NEXT(_dr, dr_entry)) {
+ if (_dr != dr)
+ NDDR_LOCK(_dr);
+ if (_dr == dr || _dr->ifp != dr->ifp) {
+ if (_dr != dr)
+ NDDR_UNLOCK(_dr);
+ continue;
+ }
+
+ if ((_dr->stateflags & NDDRF_INSTALLED) ||
+ _dr->genid == -1) {
+ if (_dr != dr)
+ NDDR_UNLOCK(_dr);
+ break;
+ }
+ if (_dr != dr)
+ NDDR_UNLOCK(_dr);
+ }
+
+ /* If none so far, schedule it to be installed below */
+ if (_dr == NULL && dr->ifp != NULL &&
+ !(dr->ifp->if_eflags & IFEF_IPV6_ROUTER)) {
+ dr->genid = -1;
+ ++update;
+ nd6log2((LOG_INFO, "%s: possible router %s, "
+ "static=%d (unreachable)\n", if_name(dr->ifp),
+ ip6_sprintf(&dr->rtaddr),
+ (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
+ }
+ NDDR_UNLOCK(dr);
+ }
+
+ dr = selected_dr;
+ if (dr != NULL) {
+ nd6log2((LOG_INFO, "%s: considering primary default router %s, "
+ "static=%d [round 1]\n", if_name(dr->ifp),
+ ip6_sprintf(&dr->rtaddr),
+ (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
+ }
+
+ /*
+ * If none of the default routers was found to be reachable,
+ * round-robin the list regardless of preference, except when
+ * Scoped Routing is enabled per case (c).
+ *
+ * Otherwise, if we have an installed router, check if the selected
+ * (reachable) router should really be preferred to the installed one.
+ * We only prefer the new router when the old one is not reachable
+ * or when the new one has a really higher preference value.
+ */
+ if (!ip6_doscopedroute && selected_dr == NULL) {
+ if (installed_dr == NULL ||
+ !TAILQ_NEXT(installed_dr, dr_entry)) {
+ selected_dr = TAILQ_FIRST(&nd_defrouter);
+ if (selected_dr)
+ NDDR_ADDREF(selected_dr);
+ } else {
+ selected_dr = TAILQ_NEXT(installed_dr, dr_entry);
+ if (selected_dr)
+ NDDR_ADDREF(selected_dr);
+ }
+ } else if (selected_dr != NULL && installed_dr != NULL) {
+ lck_mtx_unlock(nd6_mutex);
+ rt = nd6_lookup(&installed_dr->rtaddr, 0, installed_dr->ifp, 0);
+ if (rt) {
+ RT_LOCK_ASSERT_HELD(rt);
+ if ((ln = (struct llinfo_nd6 *)rt->rt_llinfo) &&
+ ND6_IS_LLINFO_PROBREACH(ln) &&
+ (!ip6_doscopedroute ||
+ installed_dr->ifp == nd6_defifp) &&
+ rtpref(selected_dr) <= rtpref(installed_dr)) {
+ NDDR_REMREF(selected_dr);
+ selected_dr = installed_dr;
+ NDDR_ADDREF(selected_dr);
+ }
+ RT_REMREF_LOCKED(rt);
+ RT_UNLOCK(rt);
+ rt = NULL;
+ found_installedrt = TRUE;
+ }
+ lck_mtx_lock(nd6_mutex);
+ }
+
+ if (ip6_doscopedroute) {
+ /*
+ * If the installed primary router is not on the current
+ * IPv6 default interface, demote it to a scoped entry.
+ */
+ if (installed_dr != NULL && installed_dr->ifp != nd6_defifp &&
+ !(installed_dr->stateflags & NDDRF_IFSCOPE)) {
+ if (selected_dr != NULL &&
+ selected_dr->ifp != nd6_defifp) {
+ NDDR_REMREF(selected_dr);
+ selected_dr = NULL;
+ }
+ ++update;
+ }
+
+ /*
+ * If the selected router is currently scoped, make sure
+ * we update (it needs to be promoted to primary.)
+ */
+ if (selected_dr != NULL &&
+ (selected_dr->stateflags & NDDRF_IFSCOPE))
+ ++update;
+
+ /*
+ * If the installed router is no longer reachable, remove
+ * it and install the selected router instead.
+ */
+ if (installed_dr != NULL
+ && selected_dr != NULL
+ && installed_dr != selected_dr
+ && found_installedrt == FALSE
+ && installed_dr->ifp == selected_dr->ifp) {
+ /* skip it below */
+ installed_dr0 = installed_dr;
+ /* NB: we previousled referenced installed_dr */
+ installed_dr = NULL;
+ selected_dr->genid = -1;
+ ++update;
+ }
+ }
+
+ /*
+ * If Scoped Routing is enabled and there's nothing to update,
+ * just return. Otherwise, if Scoped Routing is disabled and if
+ * the selected router is different than the installed one,
+ * remove the installed router and install the selected one.
+ */
+ dr = selected_dr;
+ VERIFY(dr != NULL || ip6_doscopedroute);
+ if (!ip6_doscopedroute || !update) {
+ if (dr == NULL)
+ goto out;
+
+ if (dr != installed_dr) {
+ nd6log2((LOG_INFO, "%s: no update, selected router %s, "
+ "installed router %s\n", if_name(dr->ifp),
+ ip6_sprintf(&dr->rtaddr), installed_dr != NULL ?
+ ip6_sprintf(&installed_dr->rtaddr) : "NONE"));
+ } else {
+ nd6log2((LOG_INFO, "%s: no update, router is %s\n",
+ if_name(dr->ifp), ip6_sprintf(&dr->rtaddr)));
+ }
+ if (!ip6_doscopedroute && installed_dr != dr) {
+ /*
+ * No need to ADDREF dr because at this point
+ * dr points to selected_dr, which already holds
+ * a reference.
+ */
+ lck_mtx_unlock(nd6_mutex);
+ if (installed_dr) {
+ defrouter_delreq(installed_dr);
+ }
+ defrouter_addreq(dr, FALSE);
+ lck_mtx_lock(nd6_mutex);
+ }
+ goto out;
+ }
+
+ /*
+ * Scoped Routing is enabled and we need to update. The selected
+ * router needs to be installed as primary/non-scoped entry. If
+ * there is any existing entry that is non-scoped, remove it from
+ * the routing table and reinstall it as scoped entry.
+ */
+ if (dr != NULL) {
+ nd6log2((LOG_INFO, "%s: considering primary default router %s, "
+ "static=%d [round 2]\n", if_name(dr->ifp),
+ ip6_sprintf(&dr->rtaddr),
+ (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
+ }
+
+ /*
+ * On the following while loops we use two flags:
+ * dr->genid
+ * NDDRF_PROCESSED
+ *
+ * genid is used to skip entries that are not to be added/removed on the
+ * second while loop.
+ * NDDRF_PROCESSED is used to skip entries that were already
+ * processed.
+ * This is necessary because we drop the nd6_mutex and start the while
+ * loop again.
+ */
+ TAILQ_FOREACH(dr, &nd_defrouter, dr_entry) {
+ NDDR_LOCK(dr);
+ VERIFY((dr->stateflags & NDDRF_PROCESSED) == 0);
+ NDDR_UNLOCK(dr);
+ }
+ /* Remove conflicting entries */
+ dr = TAILQ_FIRST(&nd_defrouter);
+ while (dr) {
+ NDDR_LOCK(dr);
+ if (!(dr->stateflags & NDDRF_INSTALLED) ||
+ dr->stateflags & NDDRF_PROCESSED) {
+ NDDR_UNLOCK(dr);
+ dr = TAILQ_NEXT(dr, dr_entry);
+ continue;
+ }
+ dr->stateflags |= NDDRF_PROCESSED;
+
+ /* A NULL selected_dr will remove primary default route */
+ if ((dr == selected_dr && (dr->stateflags & NDDRF_IFSCOPE)) ||
+ (dr != selected_dr && !(dr->stateflags & NDDRF_IFSCOPE))) {
+ NDDR_ADDREF_LOCKED(dr);
+ NDDR_UNLOCK(dr);
+ lck_mtx_unlock(nd6_mutex);
+ defrouter_delreq(dr);
+ lck_mtx_lock(nd6_mutex);
+ NDDR_LOCK(dr);
+ if (dr && dr != installed_dr0)
+ dr->genid = -1;
+ NDDR_UNLOCK(dr);
+ NDDR_REMREF(dr);
+ /*
+ * Since we lost nd6_mutex, we have to start over.
+ */
+ dr = TAILQ_FIRST(&nd_defrouter);
+ continue;
+ }
+ NDDR_UNLOCK(dr);
+ dr = TAILQ_NEXT(dr, dr_entry);
+ }
+
+ /* -1 is a special number, make sure we don't use it for genid */
+ if (++nd6_defrouter_genid == -1)
+ nd6_defrouter_genid = 1;
+
+ TAILQ_FOREACH(dr, &nd_defrouter, dr_entry) {
+ NDDR_LOCK(dr);
+ dr->stateflags &= ~NDDRF_PROCESSED;
+ NDDR_UNLOCK(dr);
+ }
+ /* Add the entries back */
+ dr = TAILQ_FIRST(&nd_defrouter);
+ while (dr) {
+ struct nd_defrouter *_dr;
+
+ NDDR_LOCK(dr);
+ if (dr->stateflags & NDDRF_PROCESSED ||
+ dr->genid != -1) {
+ NDDR_UNLOCK(dr);
+ dr = TAILQ_NEXT(dr, dr_entry);
+ continue;
+ }
+ dr->stateflags |= NDDRF_PROCESSED;
+
+ /* Handle case (b) */
+ for (_dr = TAILQ_FIRST(&nd_defrouter); _dr;
+ _dr = TAILQ_NEXT(_dr, dr_entry)) {
+ if (_dr == dr)
+ continue;
+ /*
+ * This is safe because we previously checked if
+ * _dr == dr.
+ */
+ NDDR_LOCK(_dr);
+ if (_dr->ifp == dr->ifp && rtpref(_dr) >= rtpref(dr) &&
+ (_dr->stateflags & NDDRF_INSTALLED)) {
+ NDDR_ADDREF_LOCKED(_dr);
+ NDDR_UNLOCK(_dr);
+ break;
+ }
+ NDDR_UNLOCK(_dr);
+ }
+
+ /* If same preference and i/f, static entry takes precedence */
+ if (_dr != NULL && rtpref(_dr) == rtpref(dr) &&
+ !(_dr->stateflags & NDDRF_STATIC) &&
+ (dr->stateflags & NDDRF_STATIC)) {
+ lck_mtx_unlock(nd6_mutex);
+ defrouter_delreq(_dr);
+ lck_mtx_lock(nd6_mutex);
+ NDDR_REMREF(_dr);
+ _dr = NULL;
+ }
+
+ if (_dr == NULL && !(dr->stateflags & NDDRF_INSTALLED)) {
+ NDDR_ADDREF_LOCKED(dr);
+ NDDR_UNLOCK(dr);
+ lck_mtx_unlock(nd6_mutex);
+ defrouter_addreq(dr, (selected_dr == NULL ||
+ dr->ifp != selected_dr->ifp));
+ dr->genid = nd6_defrouter_genid;
+ lck_mtx_lock(nd6_mutex);
+ NDDR_REMREF(dr);
+ /*
+ * Since we lost nd6_mutex, we have to start over.
+ */
+ dr = TAILQ_FIRST(&nd_defrouter);
+ continue;
+ }
+ NDDR_UNLOCK(dr);
+ dr = TAILQ_NEXT(dr, dr_entry);
+ }
+out:
+ TAILQ_FOREACH(dr, &nd_defrouter, dr_entry) {
+ NDDR_LOCK(dr);
+ dr->stateflags &= ~NDDRF_PROCESSED;
+ NDDR_UNLOCK(dr);
+ }
+ if (selected_dr)
+ NDDR_REMREF(selected_dr);
+ if (installed_dr)
+ NDDR_REMREF(installed_dr);
+ if (installed_dr0)
+ NDDR_REMREF(installed_dr0);
+ lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
+ VERIFY(nd_defrouter_busy);
+ nd_defrouter_busy = FALSE;
+ if (nd_defrouter_waiters > 0) {
+ nd_defrouter_waiters = 0;
+ wakeup(nd_defrouter_waitchan);
+ }