+ /*
+ * Let's handle easy case (3) first:
+ * If default router list is empty, there's nothing to be done.
+ */
+ if (!TAILQ_FIRST(&nd_defrouter))
+ return;
+
+ /*
+ * Due to the number of times we drop nd6_mutex, we need to
+ * serialize this function.
+ */
+ while (nd_defrouter_busy) {
+ nd_defrouter_waiters++;
+ msleep(nd_defrouter_waitchan, nd6_mutex, (PZERO-1),
+ __func__, NULL);
+ lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
+ }
+ nd_defrouter_busy = TRUE;
+
+ /*
+ * 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().
+ *
+ * For all intents and purposes of Scoped Routing:
+ * selected_dr = candidate for primary router
+ * installed_dr = currently installed primary router
+ */
+ for (dr = TAILQ_FIRST(&nd_defrouter); dr;
+ dr = TAILQ_NEXT(dr, dr_entry)) {
+ boolean_t reachable, advrouter;
+ struct in6_addr rtaddr;
+ struct ifnet *drifp;
+ struct nd_defrouter *drrele;
+
+ drrele = NULL;
+ reachable = FALSE;
+ NDDR_LOCK(dr);
+ rtaddr = *(&dr->rtaddr);
+ drifp = dr->ifp;
+ advrouter = (drifp != NULL &&
+ (drifp->if_eflags & IFEF_IPV6_ROUTER));
+ NDDR_ADDREF_LOCKED(dr); /* for this for loop */
+ NDDR_UNLOCK(dr);
+
+ lck_mtx_unlock(nd6_mutex);
+ /* Callee returns a locked route upon success */
+ if ((rt = nd6_lookup(&rtaddr, 0, drifp, 0)) != NULL) {
+ RT_LOCK_ASSERT_HELD(rt);
+ if ((ln = rt->rt_llinfo) != NULL &&
+ ND6_IS_LLINFO_PROBREACH(ln)) {
+ reachable = TRUE;
+ if (selected_dr == NULL &&
+ (!ip6_doscopedroute ||
+ (drifp == nd6_defifp && !advrouter))) {
+ selected_dr = dr;
+ NDDR_ADDREF(selected_dr);
+ }
+ }
+ RT_REMREF_LOCKED(rt);
+ RT_UNLOCK(rt);
+ rt = NULL;
+ }
+ lck_mtx_lock(nd6_mutex);
+
+ /* Handle case (b) */
+ NDDR_LOCK(dr);
+ if (ip6_doscopedroute && drifp == nd6_defifp && !advrouter &&
+ (selected_dr == NULL || rtpref(dr) > rtpref(selected_dr) ||
+ (rtpref(dr) == rtpref(selected_dr) &&
+ (dr->stateflags & NDDRF_STATIC) &&
+ !(selected_dr->stateflags & NDDRF_STATIC)))) {
+ if (selected_dr) {
+ /* Release it later on */
+ VERIFY(drrele == NULL);
+ drrele = selected_dr;
+ }
+ selected_dr = dr;
+ NDDR_ADDREF_LOCKED(selected_dr);
+ }
+
+ if (!(dr->stateflags & NDDRF_INSTALLED)) {
+ /*
+ * If the router hasn't been installed and it is
+ * reachable, try to install it later on below.
+ * If it's static, try to install it anyway.
+ */
+ if (!advrouter && (reachable ||
+ (dr->stateflags & NDDRF_STATIC))) {
+ dr->genid = -1;
+ ++update;
+ nd6log2((LOG_INFO, "%s: possible router %s, "
+ "scoped=%d, static=%d\n", if_name(drifp),
+ ip6_sprintf(&rtaddr),
+ (dr->stateflags & NDDRF_IFSCOPE) ? 1 : 0,
+ (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
+ }
+ NDDR_UNLOCK(dr);
+ NDDR_REMREF(dr); /* for this for loop */
+ if (drrele != NULL)
+ NDDR_REMREF(drrele);
+ continue;
+ }
+
+ /* Record the currently installed primary/non-scoped router */
+ if (!ip6_doscopedroute || !(dr->stateflags & NDDRF_IFSCOPE)) {
+ if (installed_dr == NULL) {
+ installed_dr = dr;
+ NDDR_ADDREF_LOCKED(installed_dr);
+ } else {
+ /* this should not happen; warn for diagnosis */
+ log(LOG_ERR, "defrouter_select: more than one "
+ "%s default router is installed\n",
+ ip6_doscopedroute ? "non-scoped" : "");
+ }
+ }
+ NDDR_UNLOCK(dr);
+ NDDR_REMREF(dr); /* for this for loop */
+ if (drrele != NULL)
+ NDDR_REMREF(drrele);
+ }
+
+ /* 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) {