]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/netinet6/nd6_rtr.c
xnu-3789.31.2.tar.gz
[apple/xnu.git] / bsd / netinet6 / nd6_rtr.c
index 2e5c5eae549948a3d65cd41907fe18344ad50612..0d6c9f0448c64954535cc30e0c9c4327843e0ad2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2016 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
  *
@@ -26,9 +26,6 @@
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
 
-/*     $FreeBSD: src/sys/netinet6/nd6_rtr.c,v 1.11 2002/04/19 04:46:23 suz Exp $       */
-/*     $KAME: nd6_rtr.c,v 1.111 2001/04/27 01:37:15 jinmei Exp $       */
-
 /*
  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
  * All rights reserved.
 #include <sys/syslog.h>
 #include <sys/queue.h>
 #include <sys/mcache.h>
+#include <sys/protosw.h>
+
+#include <dev/random/randomdev.h>
 
-#include <kern/lock.h>
+#include <kern/locks.h>
 #include <kern/zalloc.h>
 #include <machine/machine_routines.h>
 
 #include <net/if.h>
+#include <net/if_var.h>
 #include <net/if_types.h>
 #include <net/if_dl.h>
 #include <net/route.h>
 
 #include <net/net_osdep.h>
 
-#define SDL(s) ((struct sockaddr_dl *)s)
-
+static void defrouter_addreq(struct nd_defrouter *, boolean_t);
+static void defrouter_delreq(struct nd_defrouter *);
 static struct nd_defrouter *defrtrlist_update_common(struct nd_defrouter *,
     boolean_t);
 static struct nd_defrouter *defrtrlist_update(struct nd_defrouter *);
 
-static struct in6_ifaddr *in6_ifadd(struct nd_prefix *, int);
-static void defrtrlist_sync(struct ifnet *);
-
-static void defrouter_select_common(struct ifnet *, int);
+static struct in6_ifaddr *in6_pfx_newpersistaddr(struct nd_prefix *, int,
+    int *);
 
 static struct nd_pfxrouter *pfxrtr_lookup(struct nd_prefix *,
        struct nd_defrouter *);
 static void pfxrtr_add(struct nd_prefix *, struct nd_defrouter *);
-static void pfxrtr_del(struct nd_pfxrouter *);
+static void pfxrtr_del(struct nd_pfxrouter *, struct nd_prefix *);
 static struct nd_pfxrouter *find_pfxlist_reachable_router(struct nd_prefix *);
 static void nd6_rtmsg(int, struct rtentry *);
 
@@ -117,7 +116,7 @@ static struct nd_prefix *nd6_prefix_equal_lookup(struct nd_prefix *, boolean_t);
 static void nd6_prefix_sync(struct ifnet *);
 
 static void in6_init_address_ltimes(struct nd_prefix *,
-    struct in6_addrlifetime *, boolean_t);
+    struct in6_addrlifetime *);
 
 static int rt6_deleteroute(struct radix_node *, void *);
 
@@ -131,8 +130,8 @@ static void ndpr_trace(struct nd_prefix *, int);
 
 extern int nd6_recalc_reachtm_interval;
 
-static struct ifnet *nd6_defifp;
-int nd6_defifindex;
+static struct ifnet *nd6_defifp = NULL;
+int nd6_defifindex = 0;
 static unsigned int nd6_defrouter_genid;
 
 int ip6_use_tempaddr = 1; /* use temp addr by default for testing now */
@@ -144,9 +143,9 @@ u_int32_t ip6_temp_preferred_lifetime = DEF_TEMP_PREFERRED_LIFETIME;
 u_int32_t ip6_temp_valid_lifetime = DEF_TEMP_VALID_LIFETIME;
 /*
  * shorter lifetimes for debugging purposes.
-u_int32_t ip6_temp_preferred_lifetime = 800;
-static u_int32_t ip6_temp_valid_lifetime = 1800;
-*/
+ *     u_int32_t ip6_temp_preferred_lifetime = 800;
+ *     static u_int32_t ip6_temp_valid_lifetime = 1800;
+ */
 int ip6_temp_regen_advance = TEMPADDR_REGEN_ADVANCE;
 
 extern lck_mtx_t *nd6_mutex;
@@ -162,11 +161,11 @@ static void *nd_defrouter_waitchan = &nd_defrouter_busy;
 static int nd_defrouter_waiters = 0;
 
 /* RTPREF_MEDIUM has to be 0! */
-#define RTPREF_HIGH    1
-#define RTPREF_MEDIUM  0
-#define RTPREF_LOW     (-1)
-#define RTPREF_RESERVED        (-2)
-#define RTPREF_INVALID (-3)    /* internal */
+#define        RTPREF_HIGH     1
+#define        RTPREF_MEDIUM   0
+#define        RTPREF_LOW      (-1)
+#define        RTPREF_RESERVED (-2)
+#define        RTPREF_INVALID  (-3)    /* internal */
 
 #define        NDPR_TRACE_HIST_SIZE    32              /* size of trace history */
 
@@ -191,7 +190,7 @@ static struct zone *ndpr_zone;                      /* zone for nd_prefix */
 #define        NDPR_ZONE_MAX   64                      /* maximum elements in zone */
 #define        NDPR_ZONE_NAME  "nd6_prefix"            /* zone name */
 
-#define        NDDR_TRACE_HIST_SIZE    32              /* size of trace history */
+#define        NDDR_TRACE_HIST_SIZE    32              /* size of trace history */
 
 /* For gdb */
 __private_extern__ unsigned int nddr_trace_hist_size = NDDR_TRACE_HIST_SIZE;
@@ -280,8 +279,11 @@ nd6_rs_input(
        int lladdrlen = 0;
        union nd_opts ndopts;
 
+       /* Expect 32-bit aligned data pointer on strict-align platforms */
+       MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m);
+
        /* If I'm not a router, ignore it. */
-       if (ip6_accept_rtadv != 0 || (ifp->if_eflags & IFEF_ACCEPT_RTADVD) || ip6_forwarding != 1)
+       if (!ip6_forwarding || !(ifp->if_eflags & IFEF_IPV6_ROUTER))
                goto freeit;
 
        /* Sanity checks */
@@ -297,15 +299,15 @@ nd6_rs_input(
         * Don't update the neighbor cache, if src = :: or a non-neighbor.
         * The former case indicates that the src has no IP address assigned
         * yet.  See nd6_ns_input() for the latter case.
-        */
-       if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src))
+        */
+       if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) {
                goto freeit;
-       else {
+       else {
                struct sockaddr_in6 src_sa6;
 
-               bzero(&src_sa6, sizeof(src_sa6));
+               bzero(&src_sa6, sizeof (src_sa6));
                src_sa6.sin6_family = AF_INET6;
-               src_sa6.sin6_len = sizeof(src_sa6);
+               src_sa6.sin6_len = sizeof (src_sa6);
                src_sa6.sin6_addr = ip6->ip6_src;
                if (!nd6_is_addr_neighbor(&src_sa6, ifp, 0)) {
                        nd6log((LOG_INFO, "nd6_rs_input: "
@@ -314,18 +316,9 @@ nd6_rs_input(
                }
        }
 
-#ifndef PULLDOWN_TEST
        IP6_EXTHDR_CHECK(m, off, icmp6len, return);
        nd_rs = (struct nd_router_solicit *)((caddr_t)ip6 + off);
-#else
-       IP6_EXTHDR_GET(nd_rs, struct nd_router_solicit *, m, off, icmp6len);
-       if (nd_rs == NULL) {
-               icmp6stat.icp6s_tooshort++;
-               return;
-       }
-#endif
-
-       icmp6len -= sizeof(*nd_rs);
+       icmp6len -= sizeof (*nd_rs);
        nd6_option_init(nd_rs + 1, icmp6len, &ndopts);
        if (nd6_options(&ndopts) < 0) {
                nd6log((LOG_INFO,
@@ -349,11 +342,11 @@ nd6_rs_input(
 
        nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_ROUTER_SOLICIT, 0);
 
- freeit:
+freeit:
        m_freem(m);
        return;
 
- bad:
+bad:
        icmp6stat.icp6s_badrs++;
        m_freem(m);
 }
@@ -368,7 +361,7 @@ nd6_rs_input(
 void
 nd6_ra_input(
        struct  mbuf *m,
-       int off, 
+       int off,
        int icmp6len)
 {
        struct ifnet *ifp = m->m_pkthdr.rcvif;
@@ -379,13 +372,40 @@ nd6_ra_input(
        int mcast = 0;
        union nd_opts ndopts;
        struct nd_defrouter *dr = NULL;
-       struct timeval timenow;
+       u_int32_t mtu = 0;
+       char *lladdr = NULL;
+       u_int32_t lladdrlen = 0;
+       struct nd_prefix_list *nd_prefix_list_head = NULL;
+       u_int32_t nd_prefix_list_length = 0;
+       struct in6_ifaddr *ia6 = NULL;
+       struct nd_prefix_list *prfl;
+       struct nd_defrouter dr0;
+       u_int32_t advreachable;
+
 
-       getmicrotime(&timenow);
+       /* Expect 32-bit aligned data pointer on strict-align platforms */
+       MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m);
 
-       if (ip6_accept_rtadv == 0 && ((ifp->if_eflags & IFEF_ACCEPT_RTADVD) == 0))
+       /*
+        * Discard RA unless IFEF_ACCEPT_RTADV is set (as host), or when
+        * IFEF_IPV6_ROUTER is set (as router) but the RA is not locally
+        * generated.  For convenience, we allow locally generated (rtadvd)
+        * RAs to be processed on the advertising interface, as a router.
+        *
+        * Note that we don't test against ip6_forwarding as we could be
+        * both a host and a router on different interfaces, hence the
+        * check against the per-interface flags.
+        */
+       if (!(ifp->if_eflags & (IFEF_ACCEPT_RTADV | IFEF_IPV6_ROUTER)) ||
+           ((ifp->if_eflags & IFEF_IPV6_ROUTER) &&
+           (ia6 = ifa_foraddr6(&saddr6)) == NULL))
                goto freeit;
 
+       if (ia6 != NULL) {
+               IFA_REMREF(&ia6->ia_ifa);
+               ia6 = NULL;
+       }
+
        if (ip6->ip6_hlim != 255) {
                nd6log((LOG_ERR,
                    "nd6_ra_input: invalid hlim (%d) from %s to %s on %s\n",
@@ -401,18 +421,10 @@ nd6_ra_input(
                goto bad;
        }
 
-#ifndef PULLDOWN_TEST
        IP6_EXTHDR_CHECK(m, off, icmp6len, return);
        nd_ra = (struct nd_router_advert *)((caddr_t)ip6 + off);
-#else
-       IP6_EXTHDR_GET(nd_ra, struct nd_router_advert *, m, off, icmp6len);
-       if (nd_ra == NULL) {
-               icmp6stat.icp6s_tooshort++;
-               return;
-       }
-#endif
 
-       icmp6len -= sizeof(*nd_ra);
+       icmp6len -= sizeof (*nd_ra);
        nd6_option_init(nd_ra + 1, icmp6len, &ndopts);
        if (nd6_options(&ndopts) < 0) {
                nd6log((LOG_INFO,
@@ -421,25 +433,20 @@ nd6_ra_input(
                goto freeit;
        }
 
-    {
-       struct nd_defrouter dr0;
-       u_int32_t advreachable = nd_ra->nd_ra_reachable;
+       advreachable = nd_ra->nd_ra_reachable;
 
        /* remember if this is a multicasted advertisement */
        if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst))
                mcast = 1;
 
-       lck_rw_lock_shared(nd_if_rwlock);
-       if (ifp->if_index >= nd_ifinfo_indexlim) {
-               lck_rw_done(nd_if_rwlock);
-               goto freeit;
-       }
-       ndi = &nd_ifinfo[ifp->if_index];
+       ndi = ND_IFINFO(ifp);
+       VERIFY((NULL != ndi) && (TRUE == ndi->initialized));
+       lck_mtx_lock(&ndi->lock);
        bzero(&dr0, sizeof (dr0));
        dr0.rtaddr = saddr6;
        dr0.flags  = nd_ra->nd_ra_flags_reserved;
        dr0.rtlifetime = ntohs(nd_ra->nd_ra_router_lifetime);
-       dr0.expire = timenow.tv_sec + dr0.rtlifetime;
+       dr0.expire = net_uptime() + dr0.rtlifetime;
        dr0.ifp = ifp;
        /* unspecified or not? (RFC 2461 6.3.4) */
        if (advreachable) {
@@ -453,14 +460,22 @@ nd6_ra_input(
        }
        if (nd_ra->nd_ra_retransmit)
                ndi->retrans = ntohl(nd_ra->nd_ra_retransmit);
-       if (nd_ra->nd_ra_curhoplimit)
-               ndi->chlim = nd_ra->nd_ra_curhoplimit;
-       lck_rw_done(nd_if_rwlock);
-       ndi = NULL;
+       if (nd_ra->nd_ra_curhoplimit) {
+               if (ndi->chlim < nd_ra->nd_ra_curhoplimit) {
+                       ndi->chlim = nd_ra->nd_ra_curhoplimit;
+               } else if (ndi->chlim != nd_ra->nd_ra_curhoplimit) {
+                       nd6log((LOG_ERR,
+                           "RA with a lower CurHopLimit sent from "
+                           "%s on %s (current = %d, received = %d). "
+                           "Ignored.\n", ip6_sprintf(&ip6->ip6_src),
+                           if_name(ifp), ndi->chlim,
+                           nd_ra->nd_ra_curhoplimit));
+               }
+       }
+       lck_mtx_unlock(&ndi->lock);
        lck_mtx_lock(nd6_mutex);
        dr = defrtrlist_update(&dr0);
        lck_mtx_unlock(nd6_mutex);
-    }
 
        /*
         * prefix
@@ -471,9 +486,12 @@ nd6_ra_input(
                struct nd_prefix pr;
 
                for (pt = (struct nd_opt_hdr *)ndopts.nd_opts_pi;
-                    pt <= (struct nd_opt_hdr *)ndopts.nd_opts_pi_end;
-                    pt = (struct nd_opt_hdr *)((caddr_t)pt +
-                                               (pt->nd_opt_len << 3))) {
+                   pt <= (struct nd_opt_hdr *)ndopts.nd_opts_pi_end;
+                   pt = (struct nd_opt_hdr *)((caddr_t)pt +
+                   (pt->nd_opt_len << 3))) {
+                       struct in6_addr pi_mask;
+                       bzero(&pi_mask, sizeof(pi_mask));
+
                        if (pt->nd_opt_type != ND_OPT_PREFIX_INFORMATION)
                                continue;
                        pi = (struct nd_opt_prefix_info *)pt;
@@ -494,27 +512,38 @@ nd6_ra_input(
                                continue;
                        }
 
-                       if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix)
-                        || IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) {
+                       /*
+                        * To ignore ::/64 make sure bits beyond prefixlen
+                        * are set to zero
+                        */
+                       in6_prefixlen2mask(&pi_mask, pi->nd_opt_pi_prefix_len);
+                       pi->nd_opt_pi_prefix.s6_addr32[0] &= pi_mask.s6_addr32[0];
+                       pi->nd_opt_pi_prefix.s6_addr32[1] &= pi_mask.s6_addr32[1];
+                       pi->nd_opt_pi_prefix.s6_addr32[2] &= pi_mask.s6_addr32[2];
+                       pi->nd_opt_pi_prefix.s6_addr32[3] &= pi_mask.s6_addr32[3];
+
+                       if (IN6_IS_ADDR_UNSPECIFIED(&pi->nd_opt_pi_prefix) ||
+                           IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) ||
+                           IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) {
                                nd6log((LOG_INFO,
-                                   "nd6_ra_input: invalid prefix "
-                                   "%s, ignored\n",
+                                   "%s: invalid prefix %s, ignored\n",
+                                   __func__,
                                    ip6_sprintf(&pi->nd_opt_pi_prefix)));
                                continue;
                        }
 
-                       bzero(&pr, sizeof(pr));
+                       bzero(&pr, sizeof (pr));
                        lck_mtx_init(&pr.ndpr_lock, ifa_mtx_grp, ifa_mtx_attr);
                        NDPR_LOCK(&pr);
                        pr.ndpr_prefix.sin6_family = AF_INET6;
-                       pr.ndpr_prefix.sin6_len = sizeof(pr.ndpr_prefix);
+                       pr.ndpr_prefix.sin6_len = sizeof (pr.ndpr_prefix);
                        pr.ndpr_prefix.sin6_addr = pi->nd_opt_pi_prefix;
                        pr.ndpr_ifp = m->m_pkthdr.rcvif;
 
                        pr.ndpr_raf_onlink = (pi->nd_opt_pi_flags_reserved &
-                                             ND_OPT_PI_FLAG_ONLINK) ? 1 : 0;
+                           ND_OPT_PI_FLAG_ONLINK) ? 1 : 0;
                        pr.ndpr_raf_auto = (pi->nd_opt_pi_flags_reserved &
-                                           ND_OPT_PI_FLAG_AUTO) ? 1 : 0;
+                           ND_OPT_PI_FLAG_AUTO) ? 1 : 0;
                        pr.ndpr_plen = pi->nd_opt_pi_prefix_len;
                        pr.ndpr_vltime = ntohl(pi->nd_opt_pi_valid_time);
                        pr.ndpr_pltime =
@@ -523,19 +552,21 @@ nd6_ra_input(
                        /*
                         * Exceptions to stateless autoconfiguration processing:
                         * + nd6_accept_6to4 == 0 && address has 6to4 prefix
-                        * + ip6_only_allow_rfc4193_prefix != 0 && address not RFC 4193
+                        * + ip6_only_allow_rfc4193_prefix != 0 &&
+                        * address not RFC 4193
                         */
                        if (ip6_only_allow_rfc4193_prefix &&
                            !IN6_IS_ADDR_UNIQUE_LOCAL(&pi->nd_opt_pi_prefix)) {
                                nd6log((LOG_INFO,
-                                   "nd6_ra_input: no SLAAC on prefix %s [not RFC 4193]\n",
+                                   "nd6_ra_input: no SLAAC on prefix %s "
+                                   "[not RFC 4193]\n",
                                    ip6_sprintf(&pi->nd_opt_pi_prefix)));
                                pr.ndpr_raf_auto = 0;
-                       }
-                       else if (!nd6_accept_6to4 &&
-                                    IN6_IS_ADDR_6TO4(&pi->nd_opt_pi_prefix)) {
+                       } else if (!nd6_accept_6to4 &&
+                           IN6_IS_ADDR_6TO4(&pi->nd_opt_pi_prefix)) {
                                nd6log((LOG_INFO,
-                                   "nd6_ra_input: no SLAAC on prefix %s [6to4]\n",
+                                   "%s: no SLAAC on prefix %s "
+                                   "[6to4]\n", __func__,
                                    ip6_sprintf(&pi->nd_opt_pi_prefix)));
                                pr.ndpr_raf_auto = 0;
                        }
@@ -547,8 +578,43 @@ nd6_ra_input(
                        } else {
                                NDPR_UNLOCK(&pr);
                        }
-                       (void)prelist_update(&pr, dr, m, mcast);
+                       (void) prelist_update(&pr, dr, m, mcast);
                        lck_mtx_destroy(&pr.ndpr_lock, ifa_mtx_grp);
+
+                       /*
+                        * We have to copy the values out after the
+                        * prelist_update call since some of these values won't
+                        * be properly set until after the router advertisement
+                        * updating can vet the values.
+                        */
+                       prfl = NULL;
+                       MALLOC(prfl, struct nd_prefix_list *, sizeof (*prfl),
+                           M_TEMP, M_WAITOK | M_ZERO);
+
+                       if (prfl == NULL) {
+                               log(LOG_DEBUG, "%s: unable to MALLOC RA prefix "
+                                   "structure\n", __func__);
+                               continue;
+                       }
+
+                       /* this is only for nd6_post_msg(), otherwise unused */
+                       bcopy(&pr.ndpr_prefix, &prfl->pr.ndpr_prefix,
+                           sizeof (prfl->pr.ndpr_prefix));
+                       prfl->pr.ndpr_raf = pr.ndpr_raf;
+                       prfl->pr.ndpr_plen = pr.ndpr_plen;
+                       prfl->pr.ndpr_vltime = pr.ndpr_vltime;
+                       prfl->pr.ndpr_pltime = pr.ndpr_pltime;
+                       prfl->pr.ndpr_expire = pr.ndpr_expire;
+                       prfl->pr.ndpr_base_calendartime =
+                           pr.ndpr_base_calendartime;
+                       prfl->pr.ndpr_base_uptime = pr.ndpr_base_uptime;
+                       prfl->pr.ndpr_stateflags = pr.ndpr_stateflags;
+                       prfl->pr.ndpr_addrcnt = pr.ndpr_addrcnt;
+                       prfl->pr.ndpr_ifp = pr.ndpr_ifp;
+
+                       prfl->next = nd_prefix_list_head;
+                       nd_prefix_list_head = prfl;
+                       nd_prefix_list_length++;
                }
        }
 
@@ -556,7 +622,7 @@ nd6_ra_input(
         * MTU
         */
        if (ndopts.nd_opts_mtu && ndopts.nd_opts_mtu->nd_opt_mtu_len == 1) {
-               u_int32_t mtu = ntohl(ndopts.nd_opts_mtu->nd_opt_mtu_mtu);
+               mtu = ntohl(ndopts.nd_opts_mtu->nd_opt_mtu_mtu);
 
                /* lower bound */
                if (mtu < IPV6_MMTU) {
@@ -566,19 +632,14 @@ nd6_ra_input(
                        goto skip;
                }
 
-               lck_rw_lock_shared(nd_if_rwlock);
-               if (ifp->if_index >= nd_ifinfo_indexlim) {
-                       lck_rw_done(nd_if_rwlock);
-                       goto freeit;
-               }
-               ndi = &nd_ifinfo[ifp->if_index];
+               lck_mtx_lock(&ndi->lock);
                /* upper bound */
                if (ndi->maxmtu) {
                        if (mtu <= ndi->maxmtu) {
                                int change = (ndi->linkmtu != mtu);
 
                                ndi->linkmtu = mtu;
-                               lck_rw_done(nd_if_rwlock);
+                               lck_mtx_unlock(&ndi->lock);
                                if (change) /* in6_maxmtu may change */
                                        in6_setmaxmtu();
                        } else {
@@ -587,27 +648,22 @@ nd6_ra_input(
                                    "exceeds maxmtu %d, ignoring\n",
                                    mtu, ip6_sprintf(&ip6->ip6_src),
                                    ndi->maxmtu));
-                               lck_rw_done(nd_if_rwlock);
+                               lck_mtx_unlock(&ndi->lock);
                        }
                } else {
-                       lck_rw_done(nd_if_rwlock);
+                       lck_mtx_unlock(&ndi->lock);
                        nd6log((LOG_INFO, "nd6_ra_input: mtu option "
                            "mtu=%d sent from %s; maxmtu unknown, "
                            "ignoring\n",
                            mtu, ip6_sprintf(&ip6->ip6_src)));
                }
-               ndi = NULL;
        }
 
- skip:
-       
+skip:
+
        /*
         * Source link layer address
         */
-    {
-       char *lladdr = NULL;
-       int lladdrlen = 0;
-       
        if (ndopts.nd_opts_src_lladdr) {
                lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1);
                lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
@@ -621,7 +677,15 @@ nd6_ra_input(
                goto bad;
        }
 
-       nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_ROUTER_ADVERT, 0);
+       if (dr && dr->stateflags & NDDRF_MAPPED)
+               saddr6 = dr->rtaddr_mapped;
+
+       nd6_cache_lladdr(ifp, &saddr6, lladdr, (int)lladdrlen,
+           ND_ROUTER_ADVERT, 0);
+
+       /* Post message */
+       nd6_post_msg(KEV_ND6_RA, nd_prefix_list_head, nd_prefix_list_length,
+           mtu, lladdr, lladdrlen);
 
        /*
         * Installing a link-layer address might change the state of the
@@ -631,15 +695,21 @@ nd6_ra_input(
        lck_mtx_lock(nd6_mutex);
        pfxlist_onlink_check();
        lck_mtx_unlock(nd6_mutex);
-    }
 
- freeit:
+freeit:
        m_freem(m);
        if (dr)
                NDDR_REMREF(dr);
+
+       prfl = NULL;
+       while ((prfl = nd_prefix_list_head) != NULL) {
+               nd_prefix_list_head = prfl->next;
+               FREE(prfl, M_TEMP);
+       }
+
        return;
 
- bad:
+bad:
        icmp6stat.icp6s_badra++;
        goto freeit;
 }
@@ -650,18 +720,15 @@ nd6_ra_input(
 
 /* tell the change to user processes watching the routing socket. */
 static void
-nd6_rtmsg(cmd, rt)
-       int cmd;
-       struct rtentry *rt;
+nd6_rtmsg(int cmd, struct rtentry *rt)
 {
        struct rt_addrinfo info;
        struct ifnet *ifp = rt->rt_ifp;
 
        RT_LOCK_ASSERT_HELD(rt);
 
-       bzero((caddr_t)&info, sizeof(info));
-       /* Lock ifp for if_lladdr */
-       ifnet_lock_shared(ifp);
+       bzero((caddr_t)&info, sizeof (info));
+       /* It's not necessary to lock ifp for if_lladdr */
        info.rti_info[RTAX_DST] = rt_key(rt);
        info.rti_info[RTAX_GATEWAY] = rt->rt_gateway;
        info.rti_info[RTAX_NETMASK] = rt_mask(rt);
@@ -673,36 +740,86 @@ nd6_rtmsg(cmd, rt)
        info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr;
 
        rt_missmsg(cmd, &info, rt->rt_flags, 0);
-       ifnet_lock_done(ifp);
 }
 
-void
+static void
 defrouter_addreq(struct nd_defrouter *new, boolean_t scoped)
 {
        struct sockaddr_in6 def, mask, gate;
        struct rtentry *newrt = NULL;
        unsigned int ifscope;
        int err;
+       struct nd_ifinfo *ndi = ND_IFINFO(new->ifp);
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_NOTOWNED);
+       NDDR_LOCK_ASSERT_NOTHELD(new);
+       /*
+        * We're free to lock and unlock NDDR because our callers 
+        * are holding an extra reference for us.
+        */
 
+       NDDR_LOCK(new);
        if (new->stateflags & NDDRF_INSTALLED)
-               return;
+               goto out;
+
+       if (new->ifp->if_eflags & IFEF_IPV6_ROUTER) {
+               nd6log2((LOG_INFO, "%s: ignoring router %s, scoped=%d, "
+                   "static=%d on advertising interface\n", if_name(new->ifp),
+                   ip6_sprintf(&new->rtaddr), scoped,
+                   (new->stateflags & NDDRF_STATIC) ? 1 : 0));
+               goto out;
+       }
 
        nd6log2((LOG_INFO, "%s: adding default router %s, scoped=%d, "
            "static=%d\n", if_name(new->ifp), ip6_sprintf(&new->rtaddr),
            scoped, (new->stateflags & NDDRF_STATIC) ? 1 : 0));
 
-       Bzero(&def, sizeof(def));
-       Bzero(&mask, sizeof(mask));
-       Bzero(&gate, sizeof(gate));
+       Bzero(&def, sizeof (def));
+       Bzero(&mask, sizeof (mask));
+       Bzero(&gate, sizeof (gate));
 
        def.sin6_len = mask.sin6_len = gate.sin6_len
-               = sizeof(struct sockaddr_in6);
+               = sizeof (struct sockaddr_in6);
        def.sin6_family = mask.sin6_family = gate.sin6_family = AF_INET6;
-       gate.sin6_addr = new->rtaddr;
+
+       if (new->stateflags & NDDRF_MAPPED)
+               gate.sin6_addr = new->rtaddr_mapped;
+       else
+               gate.sin6_addr = new->rtaddr;
 
        ifscope = scoped ? new->ifp->if_index : IFSCOPE_NONE;
+       NDDR_UNLOCK(new);
+
+       /*
+        * Cellular networks may have buggy deployments
+        * with gateway IPv6 link local address with same
+        * interface identifier as the one that has been
+        * assigned for the cellular context.
+        * If gateway is same as locally configured link local
+        * interface on cellular interface, generated a different one
+        * and store it in the nd_defrouter entry and use it to work
+        * on routing table
+        */
+       if (new->ifp->if_type == IFT_CELLULAR &&
+           !(new->stateflags & NDDRF_STATIC) &&
+           !(new->stateflags & NDDRF_MAPPED) &&
+           IN6_IS_ADDR_LINKLOCAL(&gate.sin6_addr) &&
+           ndi && !(ndi->flags & ND6_IFF_PERFORMNUD)) {
+               struct in6_ifaddr *tmp_ia6 = in6ifa_ifpforlinklocal(new->ifp, 0);
+
+               if (tmp_ia6 != NULL &&
+                   !(tmp_ia6->ia6_flags & IN6_IFF_NOTMANUAL) &&
+                   IN6_ARE_ADDR_EQUAL(&tmp_ia6->ia_addr.sin6_addr,
+                       &gate.sin6_addr)) {
+                       gate.sin6_addr.s6_addr8[15] += 1;
+                       new->rtaddr_mapped = gate.sin6_addr;
+                       new->stateflags |= NDDRF_MAPPED;
+
+                       nd6log((LOG_INFO, "%s: Default router %s mapped "
+                           "to ", if_name(new->ifp), ip6_sprintf(&new->rtaddr)));
+                       nd6log((LOG_INFO, "%s\n", ip6_sprintf(&new->rtaddr_mapped)));
+               }
+       }
 
        err = rtrequest_scoped(RTM_ADD, (struct sockaddr *)&def,
            (struct sockaddr *)&gate, (struct sockaddr *)&mask,
@@ -710,20 +827,24 @@ defrouter_addreq(struct nd_defrouter *new, boolean_t scoped)
 
        if (newrt) {
                RT_LOCK(newrt);
-               nd6_rtmsg(RTM_ADD, newrt); /* tell user process */
+               nd6_rtmsg(RTM_ADD, newrt);      /* tell user process */
                RT_REMREF_LOCKED(newrt);
                RT_UNLOCK(newrt);
+               NDDR_LOCK(new);
                new->stateflags |= NDDRF_INSTALLED;
                if (ifscope != IFSCOPE_NONE)
                        new->stateflags |= NDDRF_IFSCOPE;
-               new->genid = nd6_defrouter_genid;
        } else {
                nd6log((LOG_ERR, "%s: failed to add default router "
                    "%s on %s scoped %d (errno = %d)\n", __func__,
                    ip6_sprintf(&gate.sin6_addr), if_name(new->ifp),
                    (ifscope != IFSCOPE_NONE), err));
+               NDDR_LOCK(new);
        }
        new->err = err;
+
+out:
+       NDDR_UNLOCK(new);
 }
 
 struct nd_defrouter *
@@ -736,12 +857,12 @@ defrouter_lookup(
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
        for (dr = TAILQ_FIRST(&nd_defrouter); dr;
-            dr = TAILQ_NEXT(dr, dr_entry)) {
+           dr = TAILQ_NEXT(dr, dr_entry)) {
                NDDR_LOCK(dr);
                if (dr->ifp == ifp && IN6_ARE_ADDR_EQUAL(addr, &dr->rtaddr)) {
                        NDDR_ADDREF_LOCKED(dr);
                        NDDR_UNLOCK(dr);
-                       return(dr);
+                       return (dr);
                }
                NDDR_UNLOCK(dr);
        }
@@ -754,7 +875,7 @@ defrouter_lookup(
  * This is just a subroutine function for defrouter_select(), and should
  * not be called from anywhere else.
  */
-void
+static void
 defrouter_delreq(struct nd_defrouter *dr)
 {
        struct sockaddr_in6 def, mask, gate;
@@ -763,26 +884,40 @@ defrouter_delreq(struct nd_defrouter *dr)
        int err;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_NOTOWNED);
-
+       NDDR_LOCK_ASSERT_NOTHELD(dr);
+       /*
+        * We're free to lock and unlock NDDR because our callers 
+        * are holding an extra reference for us.
+        */
+       NDDR_LOCK(dr);
        /* ifp would be NULL for the "drany" case */
        if (dr->ifp != NULL && !(dr->stateflags & NDDRF_INSTALLED))
-               return;
-
-       NDDR_LOCK_ASSERT_HELD(dr);
+               goto out;
 
        nd6log2((LOG_INFO, "%s: removing default router %s, scoped=%d, "
            "static=%d\n", dr->ifp != NULL ? if_name(dr->ifp) : "ANY",
            ip6_sprintf(&dr->rtaddr), (dr->stateflags & NDDRF_IFSCOPE) ? 1 : 0,
            (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
 
-       Bzero(&def, sizeof(def));
-       Bzero(&mask, sizeof(mask));
-       Bzero(&gate, sizeof(gate));
+       Bzero(&def, sizeof (def));
+       Bzero(&mask, sizeof (mask));
+       Bzero(&gate, sizeof (gate));
 
        def.sin6_len = mask.sin6_len = gate.sin6_len
-               = sizeof(struct sockaddr_in6);
+               = sizeof (struct sockaddr_in6);
        def.sin6_family = mask.sin6_family = gate.sin6_family = AF_INET6;
-       gate.sin6_addr = dr->rtaddr;
+
+       /*
+        * The router entry may be mapped to a different address.
+        * If that is the case, use the mapped address as gateway
+        * to do operation on the routing table.
+        * To get more context, read the related comment in
+        * defrouter_addreq
+        */
+       if (dr->stateflags & NDDRF_MAPPED)
+               gate.sin6_addr = dr->rtaddr_mapped;
+       else
+               gate.sin6_addr = dr->rtaddr;
 
        if (dr->ifp != NULL) {
                ifscope = (dr->stateflags & NDDRF_IFSCOPE) ?
@@ -790,6 +925,8 @@ defrouter_delreq(struct nd_defrouter *dr)
        } else {
                ifscope = IFSCOPE_NONE;
        }
+       NDDR_UNLOCK(dr);
+
        err = rtrequest_scoped(RTM_DELETE,
            (struct sockaddr *)&def, (struct sockaddr *)&gate,
            (struct sockaddr *)&mask, RTF_GATEWAY, &oldrt, ifscope);
@@ -805,6 +942,7 @@ defrouter_delreq(struct nd_defrouter *dr)
                    ip6_sprintf(&gate.sin6_addr), dr->ifp != NULL ?
                    if_name(dr->ifp) : "ANY", (ifscope != IFSCOPE_NONE), err));
        }
+       NDDR_LOCK(dr);
        /* ESRCH means it's no longer in the routing table; ignore it */
        if (oldrt != NULL || err == ESRCH) {
                dr->stateflags &= ~NDDRF_INSTALLED;
@@ -812,6 +950,8 @@ defrouter_delreq(struct nd_defrouter *dr)
                        dr->stateflags &= ~NDDRF_IFSCOPE;
        }
        dr->err = 0;
+out:
+       NDDR_UNLOCK(dr);
 }
 
 
@@ -832,9 +972,7 @@ defrouter_reset(void)
                        NDDR_ADDREF_LOCKED(dr);
                        NDDR_UNLOCK(dr);
                        lck_mtx_unlock(nd6_mutex);
-                       NDDR_LOCK(dr);
                        defrouter_delreq(dr);
-                       NDDR_UNLOCK(dr);
                        lck_mtx_lock(nd6_mutex);
                        NDDR_REMREF(dr);
                        dr = TAILQ_FIRST(&nd_defrouter);
@@ -845,45 +983,51 @@ defrouter_reset(void)
        }
 
        /* Nuke primary (non-scoped) default router */
-       if (ip6_doscopedroute) {
-               bzero(&drany, sizeof (drany));
-               lck_mtx_init(&drany.nddr_lock, ifa_mtx_grp, ifa_mtx_attr);
-               lck_mtx_unlock(nd6_mutex);
-               NDDR_LOCK(&drany);
-               defrouter_delreq(&drany);
-               NDDR_UNLOCK(&drany);
-               lck_mtx_destroy(&drany.nddr_lock, ifa_mtx_grp);
-               lck_mtx_lock(nd6_mutex);
-       }
-
+       bzero(&drany, sizeof (drany));
+       lck_mtx_init(&drany.nddr_lock, ifa_mtx_grp, ifa_mtx_attr);
+       lck_mtx_unlock(nd6_mutex);
+       defrouter_delreq(&drany);
+       lck_mtx_destroy(&drany.nddr_lock, ifa_mtx_grp);
+       lck_mtx_lock(nd6_mutex);
 }
 
 int
 defrtrlist_ioctl(u_long cmd, caddr_t data)
 {
-       struct in6_defrouter_32 *r_32 = (struct in6_defrouter_32 *)data;
-       struct in6_defrouter_64 *r_64 = (struct in6_defrouter_64 *)data;
        struct nd_defrouter dr0;
        unsigned int ifindex;
        struct ifnet *dr_ifp;
        int error = 0, add = 0;
 
+       /* XXX Handle mapped default router entries */
        switch (cmd) {
-       case SIOCDRADD_IN6_32:
-       case SIOCDRADD_IN6_64:
+       case SIOCDRADD_IN6_32:          /* struct in6_defrouter_32 */
+       case SIOCDRADD_IN6_64:          /* struct in6_defrouter_64 */
                ++add;
                /* FALLTHRU */
-       case SIOCDRDEL_IN6_32:
-       case SIOCDRDEL_IN6_64:
+       case SIOCDRDEL_IN6_32:          /* struct in6_defrouter_32 */
+       case SIOCDRDEL_IN6_64:          /* struct in6_defrouter_64 */
                bzero(&dr0, sizeof (dr0));
                if (cmd == SIOCDRADD_IN6_64 || cmd == SIOCDRDEL_IN6_64) {
-                       dr0.rtaddr = r_64->rtaddr.sin6_addr;
+                       struct in6_defrouter_64 *r_64 =
+                           (struct in6_defrouter_64 *)(void *)data;
+                       u_int16_t i;
+
+                       bcopy(&r_64->rtaddr.sin6_addr, &dr0.rtaddr,
+                           sizeof (dr0.rtaddr));
                        dr0.flags = r_64->flags;
-                       ifindex = r_64->if_index;
+                       bcopy(&r_64->if_index, &i, sizeof (i));
+                       ifindex = i;
                } else {
-                       dr0.rtaddr = r_32->rtaddr.sin6_addr;
+                       struct in6_defrouter_32 *r_32 =
+                           (struct in6_defrouter_32 *)(void *)data;
+                       u_int16_t i;
+
+                       bcopy(&r_32->rtaddr.sin6_addr, &dr0.rtaddr,
+                           sizeof (dr0.rtaddr));
                        dr0.flags = r_32->flags;
-                       ifindex = r_32->if_index;
+                       bcopy(&r_32->if_index, &i, sizeof (i));
+                       ifindex = i;
                }
                ifnet_head_lock_shared();
                /* Don't need to check is ifindex is < 0 since it's unsigned */
@@ -924,46 +1068,62 @@ defrtrlist_ioctl(u_long cmd, caddr_t data)
        return (error);
 }
 
+/*
+ * XXX Please make sure to remove dr from the
+ * global default router tailq list before this
+ * function call.
+ * Also ensure that you release the list reference
+ * only after calling this routine.
+ */
 void
 defrtrlist_del(struct nd_defrouter *dr)
 {
-       struct nd_defrouter *deldr = NULL;
+#if (DEVELOPMENT || DEBUG)
+       struct nd_defrouter *dr_itr = NULL;
+#endif
        struct nd_prefix *pr;
        struct ifnet *ifp = dr->ifp;
+       struct nd_ifinfo *ndi = NULL;
+       boolean_t resetmtu;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
+#if (DEVELOPMENT || DEBUG)
+       /*
+        * Verify that the router is not in the global default
+        * router list.
+        * Can't use defrouter_lookup here because that just works
+        * with address and ifp pointer.
+        * We have to compare the memory here.
+        * Also we can't use ASSERT here as that is not defined
+        * for development builds.
+        */
+       TAILQ_FOREACH(dr_itr, &nd_defrouter, dr_entry)
+               VERIFY(dr != dr_itr);
+#endif
+       ++nd6_defrouter_genid;
        /*
         * Flush all the routing table entries that use the router
         * as a next hop.
         */
-       if (!ip6_forwarding &&
-           (ip6_accept_rtadv || (ifp->if_eflags & IFEF_ACCEPT_RTADVD))) {
-               /* above is a good condition? */
-               NDDR_ADDREF(dr);
-               lck_mtx_unlock(nd6_mutex);
+       /* above is a good condition? */
+       NDDR_ADDREF(dr);
+       lck_mtx_unlock(nd6_mutex);
+       if (dr->stateflags & NDDRF_MAPPED)
+               rt6_flush(&dr->rtaddr_mapped, ifp);
+       else
                rt6_flush(&dr->rtaddr, ifp);
-               lck_mtx_lock(nd6_mutex);
-               NDDR_REMREF(dr);
-       }
-
-       if (dr == TAILQ_FIRST(&nd_defrouter))
-               deldr = dr;     /* The router is primary. */
-
-       TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
-       ++nd6_defrouter_genid;
 
+       lck_mtx_lock(nd6_mutex);
+       NDDR_REMREF(dr);
        nd6log2((LOG_INFO, "%s: freeing defrouter %s\n", if_name(dr->ifp),
            ip6_sprintf(&dr->rtaddr)));
-
        /*
         * Delete it from the routing table.
         */
        NDDR_ADDREF(dr);
        lck_mtx_unlock(nd6_mutex);
-       NDDR_LOCK(dr);
        defrouter_delreq(dr);
-       NDDR_UNLOCK(dr);
        lck_mtx_lock(nd6_mutex);
        NDDR_REMREF(dr);
 
@@ -975,33 +1135,32 @@ defrtrlist_del(struct nd_defrouter *dr)
 
                NDPR_LOCK(pr);
                if ((pfxrtr = pfxrtr_lookup(pr, dr)) != NULL)
-                       pfxrtr_del(pfxrtr);
+                       pfxrtr_del(pfxrtr, pr);
                NDPR_UNLOCK(pr);
        }
 
        pfxlist_onlink_check();
 
+       resetmtu = FALSE;
+       ndi = ND_IFINFO(ifp);
+       VERIFY((NULL != ndi) && (TRUE == ndi->initialized));
+       lck_mtx_lock(&ndi->lock);
+       VERIFY(ndi->ndefrouters >= 0);
+       if (ndi->ndefrouters > 0 && --ndi->ndefrouters == 0) {
+               nd6_ifreset(ifp);
+               resetmtu = TRUE;
+       }
+       lck_mtx_unlock(&ndi->lock);
+
        /*
-        * If the router is the primary one, choose a new one.  If Scoped
-        * Routing is enabled, always try to pick another eligible router
-        * on this interface.
+        * If the router is the primary one, choose a new one.
+        * We always try to pick another eligible router
+        * on this interface as we do scoped routing
         */
-       if ((deldr || ip6_doscopedroute) && !ip6_forwarding &&
-           (ip6_accept_rtadv || (ifp->if_eflags & IFEF_ACCEPT_RTADVD)))
-               defrouter_select(ifp);
-
-       lck_rw_lock_shared(nd_if_rwlock);
-       if (ifp->if_index < nd_ifinfo_indexlim) {
-               struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index];
-               atomic_add_32(&ndi->ndefrouters, -1);
-               if (ndi->ndefrouters < 0) {
-                       log(LOG_WARNING, "defrtrlist_del: negative "
-                           "count on %s\n", if_name(ifp));
-               }
-       }
-       lck_rw_done(nd_if_rwlock);
+       defrouter_select(ifp);
 
-       NDDR_REMREF(dr);        /* remove list reference */
+       if (resetmtu)
+               nd6_setmtu(ifp);
 }
 
 int
@@ -1048,7 +1207,9 @@ defrtrlist_del_static(struct nd_defrouter *new)
                        NDDR_REMREF(dr);
                dr = NULL;
        } else {
+               TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
                defrtrlist_del(dr);
+               NDDR_REMREF(dr);        /* remove list reference */
                NDDR_REMREF(dr);
        }
        lck_mtx_unlock(nd6_mutex);
@@ -1084,8 +1245,7 @@ rtpref(struct nd_defrouter *dr)
 }
 
 /*
- * Default Router Selection according to Section 6.3.6 of RFC 2461 and
- * draft-ietf-ipngwg-router-selection:
+ * Default Router Selection according to Section 6.3.6 of RFC 2461 and RFC 4191:
  *
  * 1) Routers that are reachable or probably reachable should be preferred.
  *    If we have more than one (probably) reachable router, prefer ones
@@ -1114,29 +1274,41 @@ rtpref(struct nd_defrouter *dr)
  * Since the code below covers both with and without router preference cases,
  * we do not need to classify the cases by ifdef.
  */
-static void
-defrouter_select_common(struct ifnet *ifp, int ignore)
+void
+defrouter_select(struct ifnet *ifp)
 {
-       struct nd_defrouter *dr, *selected_dr = NULL, *installed_dr = NULL;
-       struct nd_defrouter *installed_dr0 = NULL;
-       struct rtentry *rt = NULL;
+       struct nd_defrouter *dr = NULL;
+       struct nd_defrouter *selected_dr = NULL;
+       struct nd_defrouter *installed_dr = NULL;
        struct llinfo_nd6 *ln = NULL;
-       int  update = 0;
-       boolean_t found_installedrt = FALSE;
+       struct rtentry *rt = NULL;
+       struct nd_ifinfo *ndi = NULL;
+       unsigned int genid = 0;
+       boolean_t is_installed_reachable = FALSE;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
-       /*
-        * This function should be called only when acting as an autoconfigured
-        * host.  Although the remaining part of this function is not effective
-        * if the node is not an autoconfigured host, we explicitly exclude
-        * such cases here for safety.
-        */
-       if (ip6_forwarding || (!ignore && !ip6_accept_rtadv &&
-           !(ifp->if_eflags & IFEF_ACCEPT_RTADVD))) {
-               nd6log((LOG_WARNING,
-                   "defrouter_select: called unexpectedly (forwarding=%d, "
-                   "accept_rtadv=%d)\n", ip6_forwarding, ip6_accept_rtadv));
+       if (ifp == NULL) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. NULL interface",
+                   __func__, __LINE__));
+               return;
+       }
+
+       if (ifp == lo_ifp) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. "
+                   "Default router select called for loopback.\n",
+                   __func__, __LINE__));
+               return;
+       }
+
+       if (ifp->if_eflags & IFEF_IPV6_ROUTER) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. "
+                   "Default router select called for interface"
+                   " %s with IFEF_IPV6_ROUTER flag set\n",
+                   __func__, __LINE__, if_name(ifp)));
                return;
        }
 
@@ -1144,8 +1316,33 @@ defrouter_select_common(struct ifnet *ifp, int ignore)
         * Let's handle easy case (3) first:
         * If default router list is empty, there's nothing to be done.
         */
-       if (!TAILQ_FIRST(&nd_defrouter))
+       if (!TAILQ_FIRST(&nd_defrouter)) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. "
+                   "Default router is empty.\n", __func__, __LINE__));
+               return;
+       }
+
+       /*
+        * Take an early exit if number of routers in nd_ifinfo is
+        * 0 for the interface.
+        */
+       ndi = ND_IFINFO(ifp);
+       if (!ndi || !ndi->initialized) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. "
+                   "Interface %s's nd_ifinfo not initialized.\n",
+                   __func__, __LINE__, if_name(ifp)));
                return;
+       }
+
+       if (ndi->ndefrouters == 0) {
+               nd6log2((LOG_INFO,
+                   "%s:%d: Return early. "
+                   "%s does not have any default routers.\n",
+                   __func__, __LINE__, if_name(ifp)));
+               return;
+       }
 
        /*
         * Due to the number of times we drop nd6_mutex, we need to
@@ -1168,417 +1365,278 @@ defrouter_select_common(struct ifnet *ifp, int ignore)
         *      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;
-
-               /* Callee returns a locked route upon success */
-               reachable = FALSE;
-               NDDR_ADDREF(dr);        /* for this for loop */
-               lck_mtx_unlock(nd6_mutex);
-               if ((rt = nd6_lookup(&dr->rtaddr, 0, dr->ifp, 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 ||
-                                   dr->ifp == nd6_defifp)) {
-                                       selected_dr = dr;
-                                       NDDR_ADDREF(selected_dr);
-                               }
-                       }
-                       RT_REMREF_LOCKED(rt);
-                       RT_UNLOCK(rt);
-                       rt = NULL;
-               }
-               lck_mtx_lock(nd6_mutex);
+       genid = nd6_defrouter_genid;
+       dr = TAILQ_FIRST(&nd_defrouter);
 
-               /* Handle case (b) */
-               if (ip6_doscopedroute && dr->ifp == nd6_defifp &&
-                   (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)
-                               NDDR_REMREF(selected_dr);
-                       selected_dr = dr;
-                       NDDR_ADDREF(selected_dr);
-               }
+       while (dr != NULL) {
+               struct in6_addr rtaddr;
+               struct ifnet *drifp = NULL;
+               struct nd_defrouter *drrele = NULL;
 
-               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 (reachable || (dr->stateflags & NDDRF_STATIC)) {
-                               dr->genid = -1;
-                               ++update;
-                               nd6log2((LOG_INFO, "%s: possible router %s, "
-                                   "scoped=%d, static=%d\n", if_name(dr->ifp),
-                                   ip6_sprintf(&dr->rtaddr),
-                                   (dr->stateflags & NDDRF_IFSCOPE) ? 1 : 0,
-                                   (dr->stateflags & NDDRF_STATIC) ? 1 : 0));
-                       }
-                       NDDR_REMREF(dr);        /* for this for loop */
+               NDDR_LOCK(dr);
+               drifp = dr->ifp;
+               if (drifp != ifp) {
+                       NDDR_UNLOCK(dr);
+                       dr = TAILQ_NEXT(dr, dr_entry);
                        continue;
                }
 
-               /* Record the currently installed primary/non-scoped router */
-               if (!ip6_doscopedroute || !(dr->stateflags & NDDRF_IFSCOPE)) {
-                       if (installed_dr == NULL) {
+               /*
+                * Optimize for the common case.
+                * When the interface has only one default router
+                * there's no point checking for reachability as
+                * there's nothing else to choose from.
+                */
+               if (ndi->ndefrouters == 1) {
+                       nd6log2((LOG_INFO,
+                           "%s:%d: Fast forward default router selection "
+                           "as interface %s has learned only one default "
+                           "router and there's nothing else to choose from.\n",
+                           __func__, __LINE__, if_name(ifp)));
+                       VERIFY(selected_dr == NULL && installed_dr == NULL);
+                       selected_dr = dr;
+                       if (dr->stateflags & NDDRF_INSTALLED)
                                installed_dr = dr;
-                               NDDR_ADDREF(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_REMREF(dr);        /* for this for loop */
-       }
-
-       /* 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_ADDREF_LOCKED(selected_dr);
                        NDDR_UNLOCK(dr);
-                       continue;
+                       goto install_route;
                }
 
-               /* 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 (dr->stateflags & NDDRF_MAPPED)
+                       rtaddr = dr->rtaddr_mapped;
+               else
+                       rtaddr = dr->rtaddr;
 
-               /* If none so far, schedule it to be installed below */
-               if (_dr == NULL) {
-                       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_ADDREF_LOCKED(dr); /* for this for loop */
                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)
+               /* Callee returns a locked route upon success */
+               if (selected_dr == NULL) {
+                       lck_mtx_unlock(nd6_mutex);
+                       if ((rt = nd6_lookup(&rtaddr, 0, drifp, 0)) != NULL &&
+                           (ln = rt->rt_llinfo) != NULL &&
+                           ND6_IS_LLINFO_PROBREACH(ln)) {
+                               RT_LOCK_ASSERT_HELD(rt);
+                               selected_dr = dr;
                                NDDR_ADDREF(selected_dr);
+                       }
+                       lck_mtx_lock(nd6_mutex);
                }
-       } 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.
+                * Handle case (b)
+                * When there are more than one routers on the same link, the one with
+                * the highest router preference will be installed.
+                * Since the list is in decreasing order of preference:
+                * 1) If selected_dr is not NULL, only use dr if it is static and has
+                *    equal preference and selected_dr is not static.
+                * 2) Else if selected_dr is NULL, and dr is static make selected_dr = dr
                 */
-               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;
+               NDDR_LOCK(dr);
+               if (((selected_dr && (rtpref(dr) >= rtpref(selected_dr)) &&
+                    !(selected_dr->stateflags & NDDRF_STATIC)) ||
+                    (selected_dr == NULL)) &&
+                   (dr->stateflags & NDDRF_STATIC)) {
+                       if (selected_dr) {
+                               /* Release it later on */
+                               VERIFY(drrele == NULL);
+                               drrele = selected_dr;
                        }
-                       ++update;
+                       selected_dr = dr;
+                       NDDR_ADDREF_LOCKED(selected_dr);
                }
 
-               /*
-                * 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;
+               /* Record the currently installed router */
+               if (dr->stateflags & NDDRF_INSTALLED) {
+                       if (installed_dr == NULL) {
+                               installed_dr = dr;
+                               NDDR_ADDREF_LOCKED(installed_dr);
+                               if (dr->stateflags & NDDRF_MAPPED)
+                                       rtaddr = installed_dr->rtaddr_mapped;
+                               else
+                                       rtaddr = installed_dr->rtaddr;
+                               NDDR_UNLOCK(dr);
+                               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))
+                                               is_installed_reachable = TRUE;
+
+                                       RT_REMREF_LOCKED(rt);
+                                       RT_UNLOCK(rt);
+                                       rt = NULL;
+                               }
+                               lck_mtx_lock(nd6_mutex);
+                       } else {
+                               /* this should not happen; warn for diagnosis */
+                               nd6log((LOG_ERR, "defrouter_select: more than one "
+                                   "default router is installed for interface :%s.\n",
+                                   if_name(ifp)));
+                               NDDR_UNLOCK(dr);
+                       }
+               } else
+                       NDDR_UNLOCK(dr);
+
+               NDDR_REMREF(dr);        /* for this for loop */
+               if (drrele != NULL)
+                       NDDR_REMREF(drrele);
 
                /*
-                * If the installed router is no longe reachable, remove
-                * it and install the selected router instead.
+                * Check if the list changed when we gave up
+                * the nd6_mutex lock
                 */
-               if (installed_dr != NULL && selected_dr != NULL &&
-                   installed_dr != selected_dr && found_installedrt == FALSE) {
-                       installed_dr0 = installed_dr;   /* skip it below */
-                       /* 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(genid != nd6_defrouter_genid) {
+                       if (selected_dr) {
+                               NDDR_REMREF(selected_dr);
+                               selected_dr = NULL;
+                       }
 
-               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) {
-                               NDDR_LOCK(installed_dr);
-                               defrouter_delreq(installed_dr);
-                               NDDR_UNLOCK(installed_dr);
+                               NDDR_REMREF(installed_dr);
+                               installed_dr = NULL;
                        }
-                       NDDR_LOCK(dr);
-                       defrouter_addreq(dr, FALSE);
-                       NDDR_UNLOCK(dr);
-                       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));
+                       if (ndi->ndefrouters == 0) {
+                               nd6log2((LOG_INFO,
+                                   "%s:%d: Interface %s no longer "
+                                   "has any default routers. Abort.\n",
+                                   __func__, __LINE__, if_name(ifp)));
+                               goto out;
+                       }
+                       nd6log2((LOG_INFO,
+                           "%s:%d: Iterate default router list again "
+                           "for interface %s, as the list seems to have "
+                           "changed during release-reaquire of global "
+                           "nd6_mutex lock.\n",
+                           __func__, __LINE__, if_name(ifp)));
+
+                       is_installed_reachable = FALSE;
+                       genid = nd6_defrouter_genid;
+                       dr = TAILQ_FIRST(&nd_defrouter);
+               } else {
+                       dr = TAILQ_NEXT(dr, dr_entry);
+               }
        }
 
        /*
-        * 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.
+        * If none of the default routers was found to be reachable,
+        * round-robin the list regardless of preference.
+        * Please note selected_dr equal to NULL implies that even
+        * installed default router is not reachable
         */
-       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;
+       if (selected_dr == NULL) {
+               if (installed_dr) {
+                       for (dr = TAILQ_NEXT(installed_dr, dr_entry); dr;
+                           dr = TAILQ_NEXT(dr, dr_entry)) {
+                               if (installed_dr->ifp != dr->ifp)
+                                       continue;
+                               selected_dr = dr;
+                               break;
+                       }
                }
-               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);
-                       NDDR_LOCK(dr);
-                       defrouter_delreq(dr);
-                       NDDR_UNLOCK(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;
+               /*
+                * If none was installed or the installed one if the last
+                * one on the list, select the first one from the list
+                */
+               if ((installed_dr == NULL) || (selected_dr == NULL)) {
+                       for (dr = TAILQ_FIRST(&nd_defrouter); dr;
+                           dr = TAILQ_NEXT(dr, dr_entry)) {
+                               if (dr->ifp == ifp) {
+                                       selected_dr = dr;
+                                       break;
+                               }
+                       }
                }
-               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;
+               if ((selected_dr == NULL) && (installed_dr == NULL)) {
+                       nd6log2((LOG_INFO,
+                           "%s:%d: Between release and reaquire of global "
+                           "nd6_mutex lock, the list seems to have changed "
+                           "and it does not have any default routers for "
+                           "interface %s.\n",
+                           __func__, __LINE__, if_name(ifp)));
+                       goto out;
                }
-               dr->stateflags |= NDDRF_PROCESSED;
 
-               /* Handle case (b) */
-               for (_dr = TAILQ_FIRST(&nd_defrouter); _dr;
-                    _dr = TAILQ_NEXT(_dr, dr_entry)) {
-                       if (_dr == dr)
-                               continue;
+               if (selected_dr != installed_dr)
+                       NDDR_ADDREF(selected_dr);
+       } else if (installed_dr != NULL) {
+               if (installed_dr != selected_dr) {
                        /*
-                        * This is safe because we previously checked if
-                        * _dr == dr.
+                        * This means that selected default router is reachable
+                        * while installed one may or may not be.
+                        * Static router should always be considered as reachable
+                        * for router selection process.
                         */
-                       NDDR_LOCK(_dr);
-                       if (_dr->ifp == dr->ifp && rtpref(_dr) >= rtpref(dr) &&
-                           (_dr->stateflags & NDDRF_INSTALLED)) {
-                               NDDR_ADDREF_LOCKED(_dr);
-                               NDDR_UNLOCK(_dr);
-                               break;
+                       if ((installed_dr->stateflags & NDDRF_STATIC) &&
+                           rtpref(installed_dr) >= rtpref(selected_dr)) {
+                               NDDR_REMREF(selected_dr);
+                               selected_dr = installed_dr;
+                       } else if (is_installed_reachable) {
+                               if (rtpref(selected_dr) <= rtpref(installed_dr)) {
+                                       NDDR_REMREF(selected_dr);
+                                       selected_dr = installed_dr;
+                               }
                        }
-                       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);
-                       NDDR_LOCK(_dr);
-                       defrouter_delreq(_dr);
-                       NDDR_UNLOCK(_dr);
-                       lck_mtx_lock(nd6_mutex);
-                       NDDR_REMREF(_dr);
-                       _dr = NULL;
+               } else {
+                       NDDR_REMREF(selected_dr);
                }
+       }
 
-               if (_dr == NULL && !(dr->stateflags & NDDRF_INSTALLED)) {
-                       NDDR_ADDREF_LOCKED(dr);
-                       NDDR_UNLOCK(dr);
-                       lck_mtx_unlock(nd6_mutex);
-                       NDDR_LOCK(dr);
-                       defrouter_addreq(dr, (selected_dr == NULL ||
-                           dr->ifp != selected_dr->ifp));
-                       dr->genid = nd6_defrouter_genid;
-                       NDDR_UNLOCK(dr);
-                       lck_mtx_lock(nd6_mutex);
-                       NDDR_REMREF(dr);
-                       /*
-                        * Since we lost nd6_mutex, we have to start over.
-                        */
-                       dr = TAILQ_FIRST(&nd_defrouter);
-                       continue;
+install_route: 
+       /*
+        * If the selected router is different than the installed one,
+        * remove the installed router and install the selected one.
+        * Note that the selected router is never NULL here.
+        * Else check if the route entry scope has to be changed.
+        */
+       lck_mtx_unlock(nd6_mutex);
+       if (installed_dr != selected_dr) {
+               nd6log((LOG_INFO,
+                   "%s:%d: Found a better router for interface "
+                   "%s. Installing new default route.\n",
+                   __func__, __LINE__, if_name(ifp)));
+               if (installed_dr != NULL) {
+                       defrouter_delreq(installed_dr);
                }
-               NDDR_UNLOCK(dr);
-               dr = TAILQ_NEXT(dr, dr_entry);
+               /*
+                * Install scoped route if the interface is not
+                * the default nd6 interface.
+                */
+               defrouter_addreq(selected_dr,
+                   (selected_dr->ifp != nd6_defifp));
+       } else if (((installed_dr->stateflags & NDDRF_IFSCOPE) &&
+                   (installed_dr->ifp == nd6_defifp)) ||
+                  (!(installed_dr->stateflags & NDDRF_IFSCOPE) &&
+                   (installed_dr->ifp != nd6_defifp))) {
+               nd6log((LOG_INFO,
+                   "%s:%d: Need to reinstall default route for interface "
+                   "%s as its scope has changed.\n",
+                   __func__, __LINE__, if_name(ifp)));
+               defrouter_delreq(installed_dr);
+               defrouter_addreq(installed_dr,
+                   (installed_dr->ifp != nd6_defifp));
+       } else {
+               nd6log2((LOG_INFO,
+                   "%s:%d: No need to change the default "
+                   "route for interface %s.\n",
+                   __func__, __LINE__, if_name(ifp)));
        }
+       lck_mtx_lock(nd6_mutex);
 out:
-       TAILQ_FOREACH(dr, &nd_defrouter, dr_entry) {
-               NDDR_LOCK(dr);
-               dr->stateflags &= ~NDDRF_PROCESSED;
-               NDDR_UNLOCK(dr);
-       }
-       if (selected_dr)
+       if (selected_dr && (selected_dr != installed_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;
@@ -1588,30 +1646,27 @@ out:
        }
 }
 
-void
-defrouter_select(struct ifnet *ifp)
-{
-       return (defrouter_select_common(ifp, 0));
-}
-
 static struct nd_defrouter *
 defrtrlist_update_common(struct nd_defrouter *new, boolean_t scoped)
 {
        struct nd_defrouter *dr, *n;
        struct ifnet *ifp = new->ifp;
-       struct nd_ifinfo *ndi;
+       struct nd_ifinfo *ndi = NULL;
+       struct timeval caltime;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
        if ((dr = defrouter_lookup(&new->rtaddr, ifp)) != NULL) {
                /* entry exists */
                if (new->rtlifetime == 0) {
+                       TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
                        defrtrlist_del(dr);
+                       NDDR_REMREF(dr);        /* remove list reference */
                        NDDR_REMREF(dr);
                        dr = NULL;
                } else {
                        int oldpref = rtpref(dr);
-
+                       struct nd_defrouter *p = NULL;
                        /* override */
                        dr->flags = new->flags; /* xxx flag check */
                        dr->rtlifetime = new->rtlifetime;
@@ -1624,36 +1679,30 @@ defrtrlist_update_common(struct nd_defrouter *new, boolean_t scoped)
                         * list of routers in the same preference band, unless
                         * it's already at that position.
                         */
-                       if (ip6_doscopedroute) {
-                               struct nd_defrouter *p = NULL;
-
-                               /* same preference and scoped; just return */
-                               if (rtpref(new) == oldpref && scoped)
-                                       return (dr);
-
-                               n = TAILQ_FIRST(&nd_defrouter);
-                               while (n != NULL) {
-                                       /* preference changed; sort it */
-                                       if (rtpref(new) != oldpref)
-                                               break;
-
-                                       /* not at the top of band; sort it */
-                                       if (n != dr && rtpref(n) == oldpref &&
-                                           (!p || rtpref(p) > rtpref(n)))
-                                               break;
-
-                                       p = n;
-                                       n = TAILQ_NEXT(n, dr_entry);
-                               }
-
-                               /* nothing has changed, just return */
-                               if (n == NULL && (scoped ||
-                                   !(dr->stateflags & NDDRF_IFSCOPE)))
-                                       return (dr);
-                       } else if (rtpref(new) == oldpref) {
+                       /* same preference and scoped; just return */
+                       if (rtpref(new) == oldpref && scoped)
                                return (dr);
+
+                       n = TAILQ_FIRST(&nd_defrouter);
+                       while (n != NULL) {
+                               /* preference changed; sort it */
+                               if (rtpref(new) != oldpref)
+                                       break;
+
+                               /* not at the top of band; sort it */
+                               if (n != dr && rtpref(n) == oldpref &&
+                                               (!p || rtpref(p) > rtpref(n)))
+                                       break;
+
+                               p = n;
+                               n = TAILQ_NEXT(n, dr_entry);
                        }
 
+                       /* nothing has changed, just return */
+                       if (n == NULL && (scoped ||
+                           !(dr->stateflags & NDDRF_IFSCOPE)))
+                               return (dr);
+
                        /*
                         * preferred router may be changed, so relocate
                         * this router.
@@ -1665,12 +1714,7 @@ defrtrlist_update_common(struct nd_defrouter *new, boolean_t scoped)
                         */
                        TAILQ_REMOVE(&nd_defrouter, dr, dr_entry);
                        new->stateflags = dr->stateflags;
-                       new->stateflags &= ~NDDRF_PROCESSED;
 
-                       lck_rw_lock_shared(nd_if_rwlock);
-                       VERIFY(ifp->if_index < nd_ifinfo_indexlim);
-                       ndi = &nd_ifinfo[ifp->if_index];
-                       lck_rw_done(nd_if_rwlock);
                        n = dr;
                        goto insert;
                }
@@ -1681,22 +1725,20 @@ defrtrlist_update_common(struct nd_defrouter *new, boolean_t scoped)
 
        /* entry does not exist */
        if (new->rtlifetime == 0) {
-               return(NULL);
+               return (NULL);
        }
 
        n = nddr_alloc(M_WAITOK);
        if (n == NULL) {
-               return(NULL);
+               return (NULL);
        }
 
-       lck_rw_lock_shared(nd_if_rwlock);
-       ndi = &nd_ifinfo[ifp->if_index];
-       if (ifp->if_index >= nd_ifinfo_indexlim)
-               goto freeit;
+       ndi = ND_IFINFO(ifp);
+       VERIFY((NULL != ndi) && (TRUE == ndi->initialized));
+       lck_mtx_lock(&ndi->lock);
        if (ip6_maxifdefrouters >= 0 &&
            ndi->ndefrouters >= ip6_maxifdefrouters) {
-freeit:
-               lck_rw_done(nd_if_rwlock);
+               lck_mtx_unlock(&ndi->lock);
                nddr_free(n);
                return (NULL);
        }
@@ -1705,24 +1747,28 @@ freeit:
        NDDR_ADDREF(n); /* for the caller */
 
        ++nd6_defrouter_genid;
-       atomic_add_32(&ndi->ndefrouters, 1);
-       lck_rw_done(nd_if_rwlock);
+       ndi->ndefrouters++;
+       VERIFY(ndi->ndefrouters != 0);
+       lck_mtx_unlock(&ndi->lock);
 
        nd6log2((LOG_INFO, "%s: allocating defrouter %s\n", if_name(ifp),
            ip6_sprintf(&new->rtaddr)));
 
+       getmicrotime(&caltime);
        NDDR_LOCK(n);
-       memcpy(&n->rtaddr, &new->rtaddr, sizeof(n->rtaddr));
+       memcpy(&n->rtaddr, &new->rtaddr, sizeof (n->rtaddr));
        n->flags = new->flags;
        n->stateflags = new->stateflags;
-       n->stateflags &= ~NDDRF_PROCESSED;
        n->rtlifetime = new->rtlifetime;
        n->expire = new->expire;
+       n->base_calendartime = caltime.tv_sec;
+       n->base_uptime = net_uptime();
        n->ifp = new->ifp;
-       n->genid = new->genid;
        n->err = new->err;
        NDDR_UNLOCK(n);
 insert:
+       /* get nd6_service() to be scheduled as soon as it's convenient */
+       ++nd6_sched_timeout_want;
 
        /*
         * Insert the new router in the Default Router List;
@@ -1736,9 +1782,9 @@ insert:
 
        /* insert at the end of the group */
        for (dr = TAILQ_FIRST(&nd_defrouter); dr;
-            dr = TAILQ_NEXT(dr, dr_entry)) {
+           dr = TAILQ_NEXT(dr, dr_entry)) {
                if (rtpref(n) > rtpref(dr) ||
-                   (ip6_doscopedroute && !scoped && rtpref(n) == rtpref(dr)))
+                   (!scoped && rtpref(n) == rtpref(dr)))
                        break;
        }
        if (dr)
@@ -1746,8 +1792,7 @@ insert:
        else
                TAILQ_INSERT_TAIL(&nd_defrouter, n, dr_entry);
 
-       /* Ignore auto-configuration checks for static route entries */
-       defrouter_select_common(ifp, (n->stateflags & NDDRF_STATIC));
+       defrouter_select(ifp);
 
        return (n);
 }
@@ -1764,49 +1809,6 @@ defrtrlist_update(struct nd_defrouter *new)
        return (dr);
 }
 
-static void
-defrtrlist_sync(struct ifnet *ifp)
-{
-       struct nd_defrouter *dr, new;
-
-       lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
-
-       if (!ip6_doscopedroute) {
-               defrouter_select(ifp);
-               return;
-       }
-
-       for (dr = TAILQ_FIRST(&nd_defrouter); dr;
-            dr = TAILQ_NEXT(dr, dr_entry)) {
-               NDDR_LOCK(dr);
-               if (dr->ifp == ifp && (dr->stateflags & NDDRF_INSTALLED))
-                       break;
-               NDDR_UNLOCK(dr);
-       }
-
-       if (dr == NULL) {
-               /*
-                * Set ignore flag; the chosen default interface might
-                * not be configured to accept RAs.
-                */
-               defrouter_select_common(ifp, 1);
-       } else {
-               memcpy(&new.rtaddr, &dr->rtaddr, sizeof(new.rtaddr));
-               new.flags = dr->flags;
-               new.stateflags = dr->stateflags;
-               new.stateflags &= ~NDDRF_PROCESSED;
-               new.rtlifetime = dr->rtlifetime;
-               new.expire = dr->expire;
-               new.ifp = dr->ifp;
-               new.genid = dr->genid;
-               new.err = dr->err;
-               NDDR_UNLOCK(dr);
-               dr = defrtrlist_update_common(&new, FALSE);
-               if (dr)
-                       NDDR_REMREF(dr);
-       }
-}
-
 static struct nd_pfxrouter *
 pfxrtr_lookup(struct nd_prefix *pr, struct nd_defrouter *dr)
 {
@@ -1821,7 +1823,7 @@ pfxrtr_lookup(struct nd_prefix *pr, struct nd_defrouter *dr)
                        break;
        }
 
-       return(search);
+       return (search);
 }
 
 static void
@@ -1835,27 +1837,41 @@ pfxrtr_add(struct nd_prefix *pr, struct nd_defrouter *dr)
        new = zalloc(ndprtr_zone);
        if (new == NULL)
                return;
-       bzero(new, sizeof(*new));
+       bzero(new, sizeof (*new));
        new->router = dr;
 
        NDPR_LOCK(pr);
        LIST_INSERT_HEAD(&pr->ndpr_advrtrs, new, pfr_entry);
+       pr->ndpr_genid++;
        NDPR_UNLOCK(pr);
-       
+
        pfxlist_onlink_check();
 }
 
 static void
-pfxrtr_del(
-       struct nd_pfxrouter *pfr)
+pfxrtr_del(struct nd_pfxrouter *pfr, struct nd_prefix *pr)
 {
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
+       NDPR_LOCK_ASSERT_HELD(pr);
+       pr->ndpr_genid++;
        LIST_REMOVE(pfr, pfr_entry);
        zfree(ndprtr_zone, pfr);
 }
 
+/*
+ * The routine has been modified to atomically refresh expiry
+ * time for nd6 prefix as the part of lookup.
+ * There's a corner case where a system going
+ * in sleep gets rid of manual addresses configured in the system
+ * and then schedules the prefix for deletion.
+ * However before the prefix gets deleted, if system comes out
+ * from sleep and configures same address before prefix deletion
+ * , the later prefix deletion will remove the prefix route and
+ * the system will not be able to communicate with other IPv6
+ * neighbor nodes in the same subnet.
+ */
 struct nd_prefix *
-nd6_prefix_lookup(struct nd_prefix *pr)
+nd6_prefix_lookup(struct nd_prefix *pr, int nd6_prefix_expiry)
 {
        struct nd_prefix *search;
 
@@ -1866,6 +1882,9 @@ nd6_prefix_lookup(struct nd_prefix *pr)
                    pr->ndpr_plen == search->ndpr_plen &&
                    in6_are_prefix_equal(&pr->ndpr_prefix.sin6_addr,
                    &search->ndpr_prefix.sin6_addr, pr->ndpr_plen)) {
+                       if (nd6_prefix_expiry != ND6_PREFIX_EXPIRY_UNSPEC) {
+                               search->ndpr_expire = nd6_prefix_expiry;
+                       }
                        NDPR_ADDREF_LOCKED(search);
                        NDPR_UNLOCK(search);
                        break;
@@ -1874,74 +1893,7 @@ nd6_prefix_lookup(struct nd_prefix *pr)
        }
        lck_mtx_unlock(nd6_mutex);
 
-       return(search);
-}
-
-static void
-purge_detached(struct ifnet *ifp)
-{
-       struct nd_prefix *pr, *pr_next;
-       struct in6_ifaddr *ia;
-       struct ifaddr *ifa, *ifa_next;
-
-       lck_mtx_lock(nd6_mutex);
-
-       pr = nd_prefix.lh_first;
-repeat:
-       while (pr) {
-               pr_next = pr->ndpr_next;
-               NDPR_LOCK(pr);
-               if (pr->ndpr_ifp != ifp ||
-                   IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr) ||
-                   ((pr->ndpr_stateflags & NDPRF_DETACHED) == 0 &&
-                   !LIST_EMPTY(&pr->ndpr_advrtrs))) {
-                       NDPR_UNLOCK(pr);
-                       pr = pr_next;
-                       continue;
-               }
-               NDPR_UNLOCK(pr);
-               ifnet_lock_shared(ifp);
-               for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = ifa_next) {
-                       ifa_next = ifa->ifa_list.tqe_next;
-                       IFA_LOCK(ifa);
-                       if (ifa->ifa_addr->sa_family != AF_INET6) {
-                               IFA_UNLOCK(ifa);
-                               continue;
-                       }
-                       ia = (struct in6_ifaddr *)ifa;
-                       if ((ia->ia6_flags & IN6_IFF_AUTOCONF) ==
-                           IN6_IFF_AUTOCONF && ia->ia6_ndpr == pr) {
-                               IFA_ADDREF_LOCKED(ifa); /* for us */
-                               IFA_UNLOCK(ifa);
-                               /*
-                                * Purging the address requires writer access
-                                * to the address list, so drop the ifnet lock
-                                * now and repeat from beginning.
-                                */
-                               ifnet_lock_done(ifp);
-                               lck_mtx_unlock(nd6_mutex);
-                               in6_purgeaddr(ifa);
-                               lck_mtx_lock(nd6_mutex);
-                               IFA_REMREF(ifa); /* drop ours */
-                               pr = nd_prefix.lh_first;
-                               goto repeat;
-                       }
-                       IFA_UNLOCK(ifa);
-               }
-               ifnet_lock_done(ifp);
-               NDPR_LOCK(pr);
-               if (pr->ndpr_addrcnt == 0) {
-                       NDPR_ADDREF_LOCKED(pr);
-                       prelist_remove(pr);
-                       NDPR_UNLOCK(pr);
-                       NDPR_REMREF(pr);
-               } else {
-                       NDPR_UNLOCK(pr);
-               }
-               pr = pr_next;
-       }
-
-       lck_mtx_unlock(nd6_mutex);
+       return (search);
 }
 
 int
@@ -1952,38 +1904,21 @@ nd6_prelist_add(struct nd_prefix *pr, struct nd_defrouter *dr,
        struct ifnet *ifp = pr->ndpr_ifp;
        struct nd_ifinfo *ndi = NULL;
        int i, error;
-       struct timeval timenow;
-
-       getmicrotime(&timenow);
 
        if (ip6_maxifprefixes >= 0) {
-               lck_rw_lock_shared(nd_if_rwlock);
-               if (ifp->if_index >= nd_ifinfo_indexlim) {
-                       lck_rw_done(nd_if_rwlock);
-                       return (EINVAL);
-               }
-               ndi = &nd_ifinfo[ifp->if_index];
-               if (ndi->nprefixes >= ip6_maxifprefixes / 2) {
-                       lck_rw_done(nd_if_rwlock);
-                       purge_detached(ifp);
-                       lck_rw_lock_shared(nd_if_rwlock);
-                       /*
-                        * Refresh pointer since nd_ifinfo[] may have grown;
-                        * repeating the bounds check against nd_ifinfo_indexlim
-                        * isn't necessary since the array never shrinks.
-                        */
-                       ndi = &nd_ifinfo[ifp->if_index];
-               }
+               ndi = ND_IFINFO(ifp);
+               VERIFY((NULL != ndi) && (TRUE == ndi->initialized));
+               lck_mtx_lock(&ndi->lock);
                if (ndi->nprefixes >= ip6_maxifprefixes) {
-                       lck_rw_done(nd_if_rwlock);
-                       return(ENOMEM);
+                       lck_mtx_unlock(&ndi->lock);
+                       return (ENOMEM);
                }
-               lck_rw_done(nd_if_rwlock);
+               lck_mtx_unlock(&ndi->lock);
        }
 
        new = ndpr_alloc(M_WAITOK);
        if (new == NULL)
-               return ENOMEM;
+               return (ENOMEM);
 
        NDPR_LOCK(new);
        NDPR_LOCK(pr);
@@ -1999,9 +1934,9 @@ nd6_prelist_add(struct nd_prefix *pr, struct nd_defrouter *dr,
        if ((error = in6_init_prefix_ltimes(new)) != 0) {
                NDPR_UNLOCK(new);
                ndpr_free(new);
-               return(error);
+               return (error);
        }
-       new->ndpr_lastupdate = timenow.tv_sec;
+       new->ndpr_lastupdate = net_uptime();
        if (newp != NULL) {
                *newp = new;
                NDPR_ADDREF_LOCKED(new);        /* for caller */
@@ -2016,12 +1951,20 @@ nd6_prelist_add(struct nd_prefix *pr, struct nd_defrouter *dr,
 
        NDPR_UNLOCK(new);
 
+       /* get nd6_service() to be scheduled as soon as it's convenient */
+       ++nd6_sched_timeout_want;
+
        lck_mtx_lock(nd6_mutex);
        /* link ndpr_entry to nd_prefix list */
        LIST_INSERT_HEAD(&nd_prefix, new, ndpr_entry);
        new->ndpr_debug |= IFD_ATTACHED;
        NDPR_ADDREF(new);       /* for nd_prefix list */
 
+       lck_mtx_lock(&ndi->lock);
+       ndi->nprefixes++;
+       VERIFY(ndi->nprefixes != 0);
+       lck_mtx_unlock(&ndi->lock);
+
        /* ND_OPT_PI_FLAG_ONLINK processing */
        if (new->ndpr_raf_onlink) {
                int e;
@@ -2041,19 +1984,9 @@ nd6_prelist_add(struct nd_prefix *pr, struct nd_defrouter *dr,
                pfxrtr_add(new, dr);
        }
 
-       lck_rw_lock_shared(nd_if_rwlock);
-       /*
-        * Refresh pointer since nd_ifinfo[] may have grown;
-        * repeating the bounds check against nd_ifinfo_indexlim
-        * isn't necessary since the array never shrinks.
-        */
-       ndi = &nd_ifinfo[ifp->if_index];
-       atomic_add_32(&ndi->nprefixes, 1);
-       lck_rw_done(nd_if_rwlock);
-
        lck_mtx_unlock(nd6_mutex);
 
-       return 0;
+       return (0);
 }
 
 /*
@@ -2065,10 +1998,23 @@ prelist_remove(struct nd_prefix *pr)
        struct nd_pfxrouter *pfr, *next;
        struct ifnet *ifp = pr->ndpr_ifp;
        int e;
+       struct nd_ifinfo *ndi = NULL;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
        NDPR_LOCK_ASSERT_HELD(pr);
 
+       if (pr->ndpr_stateflags & NDPRF_DEFUNCT)
+               return;
+
+       /*
+        * If there are no more addresses, defunct the prefix.  This is needed
+        * because we don't want multiple threads calling prelist_remove() for
+        * the same prefix and this might happen because we unlock nd6_mutex
+        * down below.
+        */
+       if (pr->ndpr_addrcnt == 0)
+               pr->ndpr_stateflags |= NDPRF_DEFUNCT;
+
        /* make sure to invalidate the prefix until it is really freed. */
        pr->ndpr_vltime = 0;
        pr->ndpr_pltime = 0;
@@ -2078,8 +2024,7 @@ prelist_remove(struct nd_prefix *pr)
         * of pr->ndpr_raf_onlink and pr->ndpr_raf_auto not to confuse users
         * when executing "ndp -p".
         */
-
-       if ((pr->ndpr_stateflags & NDPRF_ONLINK)) {
+       if (pr->ndpr_stateflags & NDPRF_ONLINK) {
                NDPR_ADDREF_LOCKED(pr);
                NDPR_UNLOCK(pr);
                lck_mtx_unlock(nd6_mutex);
@@ -2096,8 +2041,14 @@ prelist_remove(struct nd_prefix *pr)
                        return;
        }
 
-       if (pr->ndpr_addrcnt > 0)
-               return; /* notice here? */
+       if (pr->ndpr_addrcnt > 0) {
+               /*
+                * The state might have changed if we called
+                * nd6_prefix_offlink().
+                */
+               pr->ndpr_stateflags &= ~NDPRF_DEFUNCT;
+               return; /* notice here? */
+       }
 
        /* unlink ndpr_entry from nd_prefix list */
        LIST_REMOVE(pr, ndpr_entry);
@@ -2106,19 +2057,15 @@ prelist_remove(struct nd_prefix *pr)
        /* free list of routers that adversed the prefix */
        for (pfr = pr->ndpr_advrtrs.lh_first; pfr; pfr = next) {
                next = pfr->pfr_next;
-               pfxrtr_del(pfr);
+               pfxrtr_del(pfr, pr);
        }
 
-       lck_rw_lock_shared(nd_if_rwlock);
-       if (ifp->if_index < nd_ifinfo_indexlim) {
-               struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index];
-               atomic_add_32(&ndi->nprefixes, -1);
-               if (ndi->nprefixes < 0) {
-                       log(LOG_WARNING, "prelist_remove: negative "
-                           "count on %s\n", if_name(ifp));
-               }
-       }
-       lck_rw_done(nd_if_rwlock);
+       ndi = ND_IFINFO(ifp);
+       VERIFY((NULL != ndi) && (TRUE == ndi->initialized));
+       lck_mtx_lock(&ndi->lock);
+       VERIFY(ndi->nprefixes > 0);
+       ndi->nprefixes--;
+       lck_mtx_unlock(&ndi->lock);
 
        /* This must not be the last reference to the nd_prefix */
        if (NDPR_REMREF_LOCKED(pr) == NULL) {
@@ -2126,7 +2073,11 @@ prelist_remove(struct nd_prefix *pr)
                /* NOTREACHED */
        }
 
-       pfxlist_onlink_check();
+       /*
+        * Don't call pfxlist_onlink_check() here because we are
+        * holding the NDPR lock and this could cause a deadlock when
+        * there are multiple threads executing pfxlist_onlink_check().
+        */
 }
 
 int
@@ -2144,7 +2095,7 @@ prelist_update(
        int newprefix = 0;
        int auth;
        struct in6_addrlifetime lt6_tmp;
-       struct timeval timenow;
+       uint64_t timenow = net_uptime();
 
        /* no need to lock "new" here, as it is local to the caller */
        NDPR_LOCK_ASSERT_NOTHELD(new);
@@ -2156,13 +2107,11 @@ prelist_update(
                 * both IP header and IP datagrams, doesn't it ?
                 */
 #if defined(M_AUTHIPHDR) && defined(M_AUTHIPDGM)
-               auth = (m->m_flags & M_AUTHIPHDR
-                    && m->m_flags & M_AUTHIPDGM) ? 1 : 0;
+               auth = (m->m_flags & M_AUTHIPHDR) && (m->m_flags & M_AUTHIPDGM);
 #endif
        }
 
-
-       if ((pr = nd6_prefix_lookup(new)) != NULL) {
+       if ((pr = nd6_prefix_lookup(new, ND6_PREFIX_EXPIRY_UNSPEC)) != NULL) {
                /*
                 * nd6_prefix_lookup() ensures that pr and new have the same
                 * prefix on a same interface.
@@ -2182,10 +2131,11 @@ prelist_update(
                if (new->ndpr_raf_onlink) {
                        pr->ndpr_vltime = new->ndpr_vltime;
                        pr->ndpr_pltime = new->ndpr_pltime;
-                       pr->ndpr_preferred = new->ndpr_preferred;
-                       pr->ndpr_expire = new->ndpr_expire;
+                       (void) in6_init_prefix_ltimes(pr); /* XXX error case? */
+                       pr->ndpr_lastupdate = net_uptime();
                }
 
+               NDPR_ADDREF_LOCKED(pr);
                if (new->ndpr_raf_onlink &&
                    (pr->ndpr_stateflags & NDPRF_ONLINK) == 0) {
                        int e;
@@ -2209,10 +2159,9 @@ prelist_update(
                } else {
                        NDPR_UNLOCK(pr);
                }
+               NDPR_REMREF(pr);
                lck_mtx_unlock(nd6_mutex);
        } else {
-               struct nd_prefix *newpr = NULL;
-
                newprefix = 1;
 
                if (new->ndpr_vltime == 0)
@@ -2220,45 +2169,28 @@ prelist_update(
                if (new->ndpr_raf_onlink == 0 && new->ndpr_raf_auto == 0)
                        goto end;
 
-               bzero(&new->ndpr_addr, sizeof(struct in6_addr));
+               bzero(&new->ndpr_addr, sizeof (struct in6_addr));
 
-               error = nd6_prelist_add(new, dr, &newpr, FALSE);
-               if (error != 0 || newpr == NULL) {
+               error = nd6_prelist_add(new, dr, &pr, FALSE);
+               if (error != 0 || pr == NULL) {
                        nd6log((LOG_NOTICE, "prelist_update: "
                            "nd6_prelist_add failed for %s/%d on %s "
-                           "errno=%d, returnpr=%p\n",
+                           "errno=%d, returnpr=0x%llx\n",
                            ip6_sprintf(&new->ndpr_prefix.sin6_addr),
-                                       new->ndpr_plen, if_name(new->ndpr_ifp),
-                                       error, newpr));
+                           new->ndpr_plen, if_name(new->ndpr_ifp),
+                           error, (uint64_t)VM_KERNEL_ADDRPERM(pr)));
                        goto end; /* we should just give up in this case. */
                }
-
-               /*
-                * XXX: from the ND point of view, we can ignore a prefix
-                * with the on-link bit being zero.  However, we need a
-                * prefix structure for references from autoconfigured
-                * addresses.  Thus, we explicitly make sure that the prefix
-                * itself expires now.
-                */
-               NDPR_LOCK(newpr);
-               if (newpr->ndpr_raf_onlink == 0) {
-                       newpr->ndpr_vltime = 0;
-                       newpr->ndpr_pltime = 0;
-                       in6_init_prefix_ltimes(newpr);
-               }
-
-               pr = newpr;
-               NDPR_UNLOCK(newpr);
        }
 
        /*
-        * Address autoconfiguration based on Section 5.5.3 of RFC 2462.
+        * Address autoconfiguration based on Section 5.5.3 of RFC 4862.
         * Note that pr must be non NULL at this point.
         */
 
        /* 5.5.3 (a). Ignore the prefix without the A bit set. */
        if (!new->ndpr_raf_auto)
-               goto afteraddrconf;
+               goto end;
 
        /*
         * 5.5.3 (b). the link-local prefix should have been ignored in
@@ -2277,16 +2209,9 @@ prelist_update(
         * list of addresses associated with the interface, and the Valid
         * Lifetime is not 0, form an address.  We first check if we have
         * a matching prefix.
-        * Note: we apply a clarification in rfc2462bis-02 here.  We only
-        * consider autoconfigured addresses while RFC2462 simply said
-        * "address".
         */
-
-       getmicrotime(&timenow);
-
        ifnet_lock_shared(ifp);
-       TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list)
-       {
+       TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
                struct in6_ifaddr *ifa6;
                u_int32_t remaininglifetime;
 
@@ -2298,7 +2223,7 @@ prelist_update(
                ifa6 = (struct in6_ifaddr *)ifa;
 
                /*
-                * We only consider autoconfigured addresses as per rfc2462bis.
+                * We only consider autoconfigured addresses as per RFC 4862.
                 */
                if (!(ifa6->ia6_flags & IN6_IFF_AUTOCONF)) {
                        IFA_UNLOCK(ifa);
@@ -2333,27 +2258,24 @@ prelist_update(
                 * are sure there is at least one matched address, we can
                 * proceed to 5.5.3. (e): update the lifetimes according to the
                 * "two hours" rule and the privacy extension.
-                * We apply some clarifications in rfc2462bis:
-                * - use remaininglifetime instead of storedlifetime as a
-                *   variable name
-                * - remove the dead code in the "two-hour" rule
                 */
-#define TWOHOUR                (120*60)
-               lt6_tmp = ifa6->ia6_lifetime;
+#define        TWOHOUR         (120*60)
+
+               /* retrieve time as uptime (last arg is 0) */
+               in6ifa_getlifetime(ifa6, &lt6_tmp, 0);
 
                if (lt6_tmp.ia6t_vltime == ND6_INFINITE_LIFETIME)
                        remaininglifetime = ND6_INFINITE_LIFETIME;
-               else if (timenow.tv_sec - ifa6->ia6_updatetime >
-                        lt6_tmp.ia6t_vltime) {
+               else if (timenow - ifa6->ia6_updatetime > lt6_tmp.ia6t_vltime) {
                        /*
                         * The case of "invalid" address.  We should usually
                         * not see this case.
                         */
                        remaininglifetime = 0;
-               } else
+               } else {
                        remaininglifetime = lt6_tmp.ia6t_vltime -
-                           (timenow.tv_sec - ifa6->ia6_updatetime);
-
+                           (timenow - ifa6->ia6_updatetime);
+               }
                /* when not updating, keep the current stored lifetime. */
                lt6_tmp.ia6t_vltime = remaininglifetime;
 
@@ -2378,7 +2300,7 @@ prelist_update(
                /* Special handling for lifetimes of temporary addresses. */
                if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0) {
                        u_int32_t maxvltime, maxpltime;
-                       
+
                        /* Constrain lifetimes to system limits. */
                        if (lt6_tmp.ia6t_vltime > ip6_temp_valid_lifetime)
                                lt6_tmp.ia6t_vltime = ip6_temp_valid_lifetime;
@@ -2393,72 +2315,47 @@ prelist_update(
                         * intervals.
                         */
                        if (ip6_temp_valid_lifetime >
-                           (u_int32_t)((timenow.tv_sec - ifa6->ia6_createtime) +
+                           (u_int32_t)((timenow - ifa6->ia6_createtime) +
                            ip6_desync_factor)) {
                                maxvltime = ip6_temp_valid_lifetime -
-                                   (timenow.tv_sec - ifa6->ia6_createtime) -
+                                   (timenow - ifa6->ia6_createtime) -
                                    ip6_desync_factor;
                        } else
                                maxvltime = 0;
                        if (ip6_temp_preferred_lifetime >
-                           (u_int32_t)((timenow.tv_sec - ifa6->ia6_createtime) +
+                           (u_int32_t)((timenow - ifa6->ia6_createtime) +
                            ip6_desync_factor)) {
                                maxpltime = ip6_temp_preferred_lifetime -
-                                   (timenow.tv_sec - ifa6->ia6_createtime) -
+                                   (timenow - ifa6->ia6_createtime) -
                                    ip6_desync_factor;
                        } else
                                maxpltime = 0;
 
-                       if (lt6_tmp.ia6t_vltime > maxvltime)
+                       if (lt6_tmp.ia6t_vltime == ND6_INFINITE_LIFETIME ||
+                           lt6_tmp.ia6t_vltime > maxvltime)
                                lt6_tmp.ia6t_vltime = maxvltime;
-                       if (lt6_tmp.ia6t_pltime > maxpltime)
+
+                       if (lt6_tmp.ia6t_pltime == ND6_INFINITE_LIFETIME ||
+                           lt6_tmp.ia6t_pltime > maxpltime)
                                lt6_tmp.ia6t_pltime = maxpltime;
                }
 
-               in6_init_address_ltimes(pr, &lt6_tmp,
-                   !!(ifa6->ia6_flags & IN6_IFF_TEMPORARY));
-               
-               ifa6->ia6_lifetime = lt6_tmp;
-               ifa6->ia6_updatetime = timenow.tv_sec;
+               in6_init_address_ltimes(pr, &lt6_tmp);
+
+               in6ifa_setlifetime(ifa6, &lt6_tmp);
+               ifa6->ia6_updatetime = timenow;
                IFA_UNLOCK(ifa);
        }
        ifnet_lock_done(ifp);
        if (ia6_match == NULL && new->ndpr_vltime) {
-               int ifidlen;
-
                /*
                 * 5.5.3 (d) (continued)
                 * No address matched and the valid lifetime is non-zero.
                 * Create a new address.
                 */
 
-               /*
-                * Prefix Length check:
-                * If the sum of the prefix length and interface identifier
-                * length does not equal 128 bits, the Prefix Information
-                * option MUST be ignored.  The length of the interface
-                * identifier is defined in a separate link-type specific
-                * document.
-                */
-               ifidlen = in6_if2idlen(ifp);
-               if (ifidlen < 0) {
-                       /* this should not happen, so we always log it. */
-                       log(LOG_ERR, "prelist_update: IFID undefined (%s)\n",
-                           if_name(ifp));
-                       goto end;
-               }
-               NDPR_LOCK(pr);
-               if (ifidlen + pr->ndpr_plen != 128) {
-                       nd6log((LOG_INFO,
-                           "prelist_update: invalid prefixlen "
-                           "%d for %s, ignored\n",
-                           pr->ndpr_plen, if_name(ifp)));
-                       NDPR_UNLOCK(pr);
-                       goto end;
-               }
-               NDPR_UNLOCK(pr);
-
-               if ((ia6 = in6_ifadd(new, mcast)) != NULL) {
+               if ((ia6 = in6_pfx_newpersistaddr(new, mcast, &error))
+                   != NULL) {
                        /*
                         * note that we should use pr (not new) for reference.
                         */
@@ -2474,7 +2371,7 @@ prelist_update(
                        /*
                         * RFC 4941 3.3 (2).
                         * When a new public address is created as described
-                        * in RFC2462, also create a new temporary address.
+                        * in RFC 4862, also create a new temporary address.
                         *
                         * RFC 4941 3.5.
                         * When an interface connects to a new link, a new
@@ -2485,7 +2382,7 @@ prelist_update(
                         */
                        if (ip6_use_tempaddr) {
                                int e;
-                               if ((e = in6_tmpifadd(ia6, 1, M_WAITOK)) != 0) {
+                               if ((e = in6_tmpifadd(ia6, 1)) != 0) {
                                        nd6log((LOG_NOTICE, "prelist_update: "
                                            "failed to create a temporary "
                                            "address, errno=%d\n",
@@ -2503,20 +2400,15 @@ prelist_update(
                        lck_mtx_lock(nd6_mutex);
                        pfxlist_onlink_check();
                        lck_mtx_unlock(nd6_mutex);
-               } else {
-                       /* just set an error. do not bark here. */
-                       error = EADDRNOTAVAIL; /* XXX: might be unused. */
                }
        }
 
-afteraddrconf:
-
 end:
        if (pr != NULL)
                NDPR_REMREF(pr);
        if (ia6_match != NULL)
                IFA_REMREF(&ia6_match->ia_ifa);
-       return error;
+       return (error);
 }
 
 /*
@@ -2630,6 +2522,28 @@ nddr_remref(struct nd_defrouter *nddr, int locked)
        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.
  */
@@ -2642,6 +2556,7 @@ ndpr_alloc(int how)
        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;
@@ -2661,6 +2576,17 @@ ndpr_free(struct nd_prefix *pr)
        } 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);
@@ -2745,10 +2671,35 @@ ndpr_remref(struct nd_prefix *ndpr, int locked)
        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)
@@ -2756,17 +2707,26 @@ 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);
 
-       for (pfxrtr = LIST_FIRST(&pr->ndpr_advrtrs); pfxrtr;
-            pfxrtr = LIST_NEXT(pfxrtr, pfr_entry)) {
+       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(&pfxrtr->router->rtaddr, 0,
-                   pfxrtr->router->ifp, 0)) != NULL) {
+               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)) {
@@ -2781,6 +2741,11 @@ find_pfxlist_reachable_router(struct nd_prefix *pr)
                }
                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);
 
@@ -2808,6 +2773,9 @@ pfxlist_onlink_check(void)
        struct in6_ifaddr *ifa;
        struct nd_defrouter *dr;
        struct nd_pfxrouter *pfxrtr = NULL;
+       int err, i, found = 0;
+       struct ifaddr **ifap = NULL;
+       struct nd_prefix *ndpr;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
@@ -2826,7 +2794,7 @@ pfxlist_onlink_check(void)
        pr = nd_prefix.lh_first;
        while (pr) {
                NDPR_LOCK(pr);
-               if (pr->ndpr_stateflags & NDPRF_PROCESSED) {
+               if (pr->ndpr_stateflags & NDPRF_PROCESSED_ONLINK) {
                        NDPR_UNLOCK(pr);
                        pr = pr->ndpr_next;
                        continue;
@@ -2834,26 +2802,27 @@ pfxlist_onlink_check(void)
                NDPR_ADDREF_LOCKED(pr);
                if (pr->ndpr_raf_onlink && find_pfxlist_reachable_router(pr) &&
                    (pr->ndpr_debug & IFD_ATTACHED)) {
-                       NDPR_UNLOCK(pr);
-                       NDPR_REMREF(pr);
+                       if (NDPR_REMREF_LOCKED(pr) == NULL)
+                               pr = NULL;
+                       else
+                               NDPR_UNLOCK(pr);
                        break;
                }
-               pr->ndpr_stateflags |= NDPRF_PROCESSED;
+               pr->ndpr_stateflags |= NDPRF_PROCESSED_ONLINK;
                NDPR_UNLOCK(pr);
                NDPR_REMREF(pr);
                /*
                 * Since find_pfxlist_reachable_router() drops the nd6_mutex, we
-                * have to start over, but the NDPRF_PROCESSED flag will stop
-                * us from checking the same prefix twice.
+                * have to start over, but the NDPRF_PROCESSED_ONLINK flag will
+                * stop us from checking the same prefix twice.
                 */
                pr = nd_prefix.lh_first;
        }
        LIST_FOREACH(prclear, &nd_prefix, ndpr_entry) {
                NDPR_LOCK(prclear);
-               prclear->ndpr_stateflags &= ~NDPRF_PROCESSED;
+               prclear->ndpr_stateflags &= ~NDPRF_PROCESSED_ONLINK;
                NDPR_UNLOCK(prclear);
        }
-
        /*
         * If we have no such prefix, check whether we still have a router
         * that does not advertise any prefixes.
@@ -2895,7 +2864,7 @@ pfxlist_onlink_check(void)
                         * set nor in static prefixes
                         */
                        if (pr->ndpr_raf_onlink == 0 ||
-                           pr->ndpr_stateflags & NDPRF_PROCESSED ||
+                           pr->ndpr_stateflags & NDPRF_PROCESSED_ONLINK ||
                            pr->ndpr_stateflags & NDPRF_STATIC) {
                                NDPR_UNLOCK(pr);
                                pr = pr->ndpr_next;
@@ -2910,14 +2879,14 @@ pfxlist_onlink_check(void)
                            find_pfxlist_reachable_router(pr) != NULL &&
                            (pr->ndpr_debug & IFD_ATTACHED))
                                pr->ndpr_stateflags &= ~NDPRF_DETACHED;
-                       pr->ndpr_stateflags |= NDPRF_PROCESSED;
+                       pr->ndpr_stateflags |= NDPRF_PROCESSED_ONLINK;
                        NDPR_UNLOCK(pr);
                        NDPR_REMREF(pr);
                        /*
                         * Since find_pfxlist_reachable_router() drops the
                         * nd6_mutex, we have to start over, but the
-                        * NDPRF_PROCESSED flag will stop us from checking
-                        * the same prefix twice.
+                        * NDPRF_PROCESSED_ONLINK flag will stop us from
+                        * checking the same prefix twice.
                         */
                        pr = nd_prefix.lh_first;
                }
@@ -2937,16 +2906,9 @@ pfxlist_onlink_check(void)
        }
        LIST_FOREACH(prclear, &nd_prefix, ndpr_entry) {
                NDPR_LOCK(prclear);
-               prclear->ndpr_stateflags &= ~NDPRF_PROCESSED;
+               prclear->ndpr_stateflags &= ~NDPRF_PROCESSED_ONLINK;
                NDPR_UNLOCK(prclear);
        }
-       VERIFY(nd_prefix_busy);
-       nd_prefix_busy = FALSE;
-       if (nd_prefix_waiters > 0) {
-               nd_prefix_waiters = 0;
-               wakeup(nd_prefix_waitchan);
-       }
-
        /*
         * Remove each interface route associated with a (just) detached
         * prefix, and reinstall the interface route for a (just) attached
@@ -2961,11 +2923,15 @@ pfxlist_onlink_check(void)
 
                NDPR_LOCK(pr);
                if (pr->ndpr_raf_onlink == 0 ||
-                   pr->ndpr_stateflags & NDPRF_STATIC) {
+                   pr->ndpr_stateflags & NDPRF_STATIC ||
+                   pr->ndpr_stateflags & NDPRF_PROCESSED_ONLINK ||
+                   pr->ndpr_stateflags & NDPRF_DEFUNCT) {
                        NDPR_UNLOCK(pr);
                        pr = pr->ndpr_next;
                        continue;
                }
+               pr->ndpr_stateflags |= NDPRF_PROCESSED_ONLINK;
+               NDPR_ADDREF_LOCKED(pr);
                if ((pr->ndpr_stateflags & NDPRF_DETACHED) != 0 &&
                    (pr->ndpr_stateflags & NDPRF_ONLINK) != 0) {
                        NDPR_UNLOCK(pr);
@@ -2978,6 +2944,7 @@ pfxlist_onlink_check(void)
                                    pr->ndpr_plen, e));
                        }
                        lck_mtx_lock(nd6_mutex);
+                       NDPR_REMREF(pr);
                        pr = nd_prefix.lh_first;
                        continue;
                }
@@ -2992,11 +2959,26 @@ pfxlist_onlink_check(void)
                                    ip6_sprintf(&pr->ndpr_prefix.sin6_addr),
                                    pr->ndpr_plen, e));
                        }
+                       NDPR_REMREF(pr);
+                       pr = nd_prefix.lh_first;
+                       continue;
                } else {
                        NDPR_UNLOCK(pr);
                }
+               NDPR_REMREF(pr);
                pr = pr->ndpr_next;
        }
+       LIST_FOREACH(prclear, &nd_prefix, ndpr_entry) {
+               NDPR_LOCK(prclear);
+               prclear->ndpr_stateflags &= ~NDPRF_PROCESSED_ONLINK;
+               NDPR_UNLOCK(prclear);
+       }
+       VERIFY(nd_prefix_busy);
+       nd_prefix_busy = FALSE;
+       if (nd_prefix_waiters > 0) {
+               nd_prefix_waiters = 0;
+               wakeup(nd_prefix_waitchan);
+       }
 
        /*
         * Changes on the prefix status might affect address status as well.
@@ -3005,13 +2987,23 @@ pfxlist_onlink_check(void)
         * detached.  Note, however, that a manually configured address should
         * always be attached.
         * The precise detection logic is same as the one for prefixes.
+        *
+        * ifnet_get_address_list_family_internal() may fail due to memory
+        * pressure, but we will eventually be called again when we receive
+        * another NA, RA, or when the link status changes.
         */
-       lck_rw_lock_shared(&in6_ifaddr_rwlock);
-       for (ifa = in6_ifaddrs; ifa; ifa = ifa->ia_next) {
-               struct nd_prefix *ndpr;
-
+       err = ifnet_get_address_list_family_internal(NULL, &ifap, AF_INET6, 0,
+           M_NOWAIT, 0);
+       if (err != 0 || ifap == NULL) {
+               nd6log((LOG_ERR, "%s: ifnet_get_address_list_family_internal "
+                   "failed", __func__));
+               return;
+       }
+       for (i = 0; ifap[i]; i++) {
+               ifa = ifatoia6(ifap[i]);
                IFA_LOCK(&ifa->ia_ifa);
-               if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) {
+               if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0 ||
+                   (ifap[i]->ifa_debug & IFD_ATTACHED) == 0) {
                        IFA_UNLOCK(&ifa->ia_ifa);
                        continue;
                }
@@ -3024,24 +3016,28 @@ pfxlist_onlink_check(void)
                        IFA_UNLOCK(&ifa->ia_ifa);
                        continue;
                }
-               NDPR_ADDREF(ndpr);
                IFA_UNLOCK(&ifa->ia_ifa);
 
                NDPR_LOCK(ndpr);
+               NDPR_ADDREF_LOCKED(ndpr);
                if (find_pfxlist_reachable_router(ndpr)) {
-                       NDPR_UNLOCK(ndpr);
-                       NDPR_REMREF(ndpr);
+                       if (NDPR_REMREF_LOCKED(ndpr) == NULL) {
+                               found = 0;
+                       } else {
+                               NDPR_UNLOCK(ndpr);
+                               found = 1;
+                       }
                        break;
                }
                NDPR_UNLOCK(ndpr);
                NDPR_REMREF(ndpr);
        }
-       if (ifa) {
-               for (ifa = in6_ifaddrs; ifa; ifa = ifa->ia_next) {
-                       struct nd_prefix *ndpr;
-
+       if (found) {
+               for (i = 0; ifap[i]; i++) {
+                       ifa = ifatoia6(ifap[i]);
                        IFA_LOCK(&ifa->ia_ifa);
-                       if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) {
+                       if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0 ||
+                           (ifap[i]->ifa_debug & IFD_ATTACHED) == 0) {
                                IFA_UNLOCK(&ifa->ia_ifa);
                                continue;
                        }
@@ -3050,15 +3046,15 @@ pfxlist_onlink_check(void)
                                IFA_UNLOCK(&ifa->ia_ifa);
                                continue;
                        }
-                       NDPR_ADDREF(ndpr);
                        IFA_UNLOCK(&ifa->ia_ifa);
                        NDPR_LOCK(ndpr);
+                       NDPR_ADDREF_LOCKED(ndpr);
                        if (find_pfxlist_reachable_router(ndpr)) {
                                NDPR_UNLOCK(ndpr);
                                IFA_LOCK(&ifa->ia_ifa);
                                if (ifa->ia6_flags & IN6_IFF_DETACHED) {
                                        ifa->ia6_flags &= ~IN6_IFF_DETACHED;
-                                       ifa->ia6_flags |= IN6_IFF_TENTATIVE;
+                                       in6_ifaddr_set_dadprogress((struct in6_ifaddr *)ifa);
                                        IFA_UNLOCK(&ifa->ia_ifa);
                                        nd6_dad_start((struct ifaddr *)ifa, 0);
                                } else {
@@ -3072,9 +3068,9 @@ pfxlist_onlink_check(void)
                        }
                        NDPR_REMREF(ndpr);
                }
-       }
-       else {
-               for (ifa = in6_ifaddrs; ifa; ifa = ifa->ia_next) {
+       } else {
+               for (i = 0; ifap[i]; i++) {
+                       ifa = ifatoia6(ifap[i]);
                        IFA_LOCK(&ifa->ia_ifa);
                        if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) {
                                IFA_UNLOCK(&ifa->ia_ifa);
@@ -3082,7 +3078,7 @@ pfxlist_onlink_check(void)
                        }
                        if (ifa->ia6_flags & IN6_IFF_DETACHED) {
                                ifa->ia6_flags &= ~IN6_IFF_DETACHED;
-                               ifa->ia6_flags |= IN6_IFF_TENTATIVE;
+                               in6_ifaddr_set_dadprogress((struct in6_ifaddr *)ifa);
                                IFA_UNLOCK(&ifa->ia_ifa);
                                /* Do we need a delay in this case? */
                                nd6_dad_start((struct ifaddr *)ifa, 0);
@@ -3091,7 +3087,7 @@ pfxlist_onlink_check(void)
                        }
                }
        }
-       lck_rw_done(&in6_ifaddr_rwlock);
+       ifnet_free_address_list(ifap);
 }
 
 static struct nd_prefix *
@@ -3137,7 +3133,7 @@ nd6_prefix_sync(struct ifnet *ifp)
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
 
-       if (!ip6_doscopedroute || ifp == NULL)
+       if (ifp == NULL)
                return;
 
        for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) {
@@ -3235,7 +3231,7 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
        struct sockaddr_in6 mask6, prefix;
        struct nd_prefix *opr;
        u_int32_t rtflags;
-       int error = 0;
+       int error = 0, prproxy = 0;
        struct rtentry *rt = NULL;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
@@ -3244,10 +3240,10 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
        NDPR_LOCK(pr);
        if ((pr->ndpr_stateflags & NDPRF_ONLINK) != 0) {
                nd6log((LOG_ERR,
-                   "nd6_prefix_onlink: %s/%d on %s scoped=%d is already "
-                    "on-link\n", ip6_sprintf(&pr->ndpr_prefix.sin6_addr),
-                    pr->ndpr_plen, if_name(pr->ndpr_ifp),
-                    (pr->ndpr_stateflags & NDPRF_IFSCOPE) ? 1 : 0);
+                   "%s: %s/%d on %s scoped=%d is already on-link\n",
+                   __func__, ip6_sprintf(&pr->ndpr_prefix.sin6_addr),
+                   pr->ndpr_plen, if_name(pr->ndpr_ifp),
+                   (pr->ndpr_stateflags & NDPRF_IFSCOPE) ? 1 : 0);
                NDPR_UNLOCK(pr);
                return (EEXIST));
        }
@@ -3262,12 +3258,7 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
        if (opr != NULL)
                NDPR_REMREF(opr);
 
-       if (!ip6_doscopedroute) {
-               /* if an interface route already exists, just return */
-               if (opr != NULL)
-                       return (0);
-               ifscope = IFSCOPE_NONE;
-       } else if (!force_scoped) {
+       if (!force_scoped) {
                /*
                 * If a primary/non-scoped interface route already exists,
                 * install the new one as a scoped entry.  If the existing
@@ -3286,8 +3277,7 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
         */
        /* search for a link-local addr */
        ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp,
-                                                     IN6_IFF_NOTREADY|
-                                                     IN6_IFF_ANYCAST);
+           IN6_IFF_NOTREADY | IN6_IFF_ANYCAST);
        if (ifa == NULL) {
                struct in6_ifaddr *ia6;
                ifnet_lock_shared(ifp);
@@ -3318,10 +3308,13 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
         * in6_ifinit() sets nd6_rtrequest to ifa_rtrequest for all ifaddrs.
         * ifa->ifa_rtrequest = nd6_rtrequest;
         */
-       bzero(&mask6, sizeof(mask6));
-       mask6.sin6_len = sizeof(mask6);
+       bzero(&mask6, sizeof (mask6));
+       mask6.sin6_len = sizeof (mask6);
        mask6.sin6_addr = pr->ndpr_mask;
        prefix = pr->ndpr_prefix;
+       if ((rt = pr->ndpr_rt) != NULL)
+               pr->ndpr_rt = NULL;
+       NDPR_ADDREF_LOCKED(pr);         /* keep reference for this routine */
        NDPR_UNLOCK(pr);
 
        IFA_LOCK_SPIN(ifa);
@@ -3339,15 +3332,25 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
 
        lck_mtx_unlock(nd6_mutex);
 
+       if (rt != NULL) {
+               rtfree(rt);
+               rt = NULL;
+       }
+
        error = rtrequest_scoped(RTM_ADD, (struct sockaddr *)&prefix,
            ifa->ifa_addr, (struct sockaddr *)&mask6, rtflags, &rt,
            ifscope);
 
+       /*
+        * Serialize the setting of NDPRF_PRPROXY.
+        */
+       lck_mtx_lock(&proxy6_lock);
+
        if (rt != NULL) {
                RT_LOCK(rt);
                nd6_rtmsg(RTM_ADD, rt);
                RT_UNLOCK(rt);
-               RT_REMREF(rt);
+               NDPR_LOCK(pr);
        } else {
                NDPR_LOCK(pr);
                nd6log((LOG_ERR, "nd6_prefix_onlink: failed to add route for a"
@@ -3355,25 +3358,71 @@ nd6_prefix_onlink_common(struct nd_prefix *pr, boolean_t force_scoped,
                    " scoped=%d, errno = %d\n",
                    ip6_sprintf(&pr->ndpr_prefix.sin6_addr),
                    pr->ndpr_plen, if_name(ifp),
-                   ip6_sprintf(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr),
+                   ip6_sprintf(&((struct sockaddr_in6 *)
+                   (void *)ifa->ifa_addr)->sin6_addr),
                    ip6_sprintf(&mask6.sin6_addr), rtflags,
                    (ifscope != IFSCOPE_NONE), error));
-               NDPR_UNLOCK(pr);
        }
+       NDPR_LOCK_ASSERT_HELD(pr);
 
-       lck_mtx_lock(nd6_mutex);
+       pr->ndpr_stateflags &= ~(NDPRF_IFSCOPE | NDPRF_PRPROXY);
 
-       NDPR_LOCK(pr);
-       pr->ndpr_stateflags &= ~NDPRF_IFSCOPE;
-       if (rt != NULL || error == EEXIST) {
+       /*
+        * TODO: If the prefix route exists, we should really find it and
+        * refer the prefix to it; otherwise ndpr_rt is NULL.
+        */
+       if (!(pr->ndpr_stateflags & NDPRF_DEFUNCT) &&
+           (rt != NULL || error == EEXIST)) {
+               struct nd_ifinfo *ndi = NULL;
+
+               VERIFY(pr->ndpr_prproxy_sols_cnt == 0);
+               VERIFY(RB_EMPTY(&pr->ndpr_prproxy_sols));
+
+               ndi = ND_IFINFO(ifp);
+               VERIFY((NULL != ndi)  && (TRUE == ndi->initialized));
+               lck_mtx_lock(&ndi->lock);
+
+               pr->ndpr_rt = rt;       /* keep reference from rtrequest */
                pr->ndpr_stateflags |= NDPRF_ONLINK;
-               if (ifscope != IFSCOPE_NONE)
+               if (ifscope != IFSCOPE_NONE) {
                        pr->ndpr_stateflags |= NDPRF_IFSCOPE;
-       }
+               } else if ((rtflags & RTF_CLONING) &&
+                   (ndi->flags & ND6_IFF_PROXY_PREFIXES) &&
+                   !IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) {
+                       /*
+                        * At present, in order for the prefix to be eligible
+                        * as a proxying/proxied prefix, we require that the
+                        * prefix route entry be marked as a cloning route with
+                        * RTF_PROXY; i.e. nd6_need_cache() needs to return
+                        * true for the interface type, hence the test for
+                        * RTF_CLONING above.
+                        */
+                       pr->ndpr_stateflags |= NDPRF_PRPROXY;
+               }
+
+               lck_mtx_unlock(&ndi->lock);
+       } else if (rt != NULL && pr->ndpr_stateflags & NDPRF_DEFUNCT)
+               rtfree(rt);
+
+       prproxy = (pr->ndpr_stateflags & NDPRF_PRPROXY);
+       VERIFY(!prproxy || !(pr->ndpr_stateflags & NDPRF_IFSCOPE));
        NDPR_UNLOCK(pr);
 
        IFA_REMREF(ifa);
 
+       /*
+        * If this is an upstream prefix, find the downstream ones (if any)
+        * and re-configure their prefix routes accordingly.  Otherwise,
+        * this could be potentially be a downstream prefix, and so find the
+        * upstream prefix, if any.
+        */
+       nd6_prproxy_prelist_update(pr, prproxy ? pr : NULL);
+
+       NDPR_REMREF(pr);        /* release reference for this routine */
+       lck_mtx_unlock(&proxy6_lock);
+
+       lck_mtx_lock(nd6_mutex);
+
        return (error);
 }
 
@@ -3392,11 +3441,10 @@ nd6_prefix_onlink_scoped(struct nd_prefix *pr, unsigned int ifscope)
 int
 nd6_prefix_offlink(struct nd_prefix *pr)
 {
-       int plen, error = 0;
+       int plen, error = 0, prproxy;
        struct ifnet *ifp = pr->ndpr_ifp;
-       struct nd_prefix *opr;
        struct sockaddr_in6 sa6, mask6, prefix;
-       struct rtentry *rt = NULL;
+       struct rtentry *rt = NULL, *ndpr_rt = NULL;
        unsigned int ifscope;
 
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_NOTOWNED);
@@ -3413,17 +3461,20 @@ nd6_prefix_offlink(struct nd_prefix *pr)
                return (EEXIST);
        }
 
-       bzero(&sa6, sizeof(sa6));
+       bzero(&sa6, sizeof (sa6));
        sa6.sin6_family = AF_INET6;
-       sa6.sin6_len = sizeof(sa6);
+       sa6.sin6_len = sizeof (sa6);
        bcopy(&pr->ndpr_prefix.sin6_addr, &sa6.sin6_addr,
-             sizeof(struct in6_addr));
-       bzero(&mask6, sizeof(mask6));
+           sizeof (struct in6_addr));
+       bzero(&mask6, sizeof (mask6));
        mask6.sin6_family = AF_INET6;
-       mask6.sin6_len = sizeof(sa6);
-       bcopy(&pr->ndpr_mask, &mask6.sin6_addr, sizeof(struct in6_addr));
+       mask6.sin6_len = sizeof (sa6);
+       bcopy(&pr->ndpr_mask, &mask6.sin6_addr, sizeof (struct in6_addr));
        prefix = pr->ndpr_prefix;
        plen = pr->ndpr_plen;
+       if ((ndpr_rt = pr->ndpr_rt) != NULL)
+               pr->ndpr_rt = NULL;
+       NDPR_ADDREF_LOCKED(pr);         /* keep reference for this routine */
        NDPR_UNLOCK(pr);
 
        ifscope = (pr->ndpr_stateflags & NDPRF_IFSCOPE) ?
@@ -3439,65 +3490,6 @@ nd6_prefix_offlink(struct nd_prefix *pr)
                RT_UNLOCK(rt);
                rtfree(rt);
 
-               /*
-                * The following check takes place only when Scoped Routing
-                * is not enabled.  There might be the same prefix on another
-                * interface, the prefix which could not be on-link just
-                * because we have the interface route (see comments in
-                * nd6_prefix_onlink).  If there's one, try to make the prefix
-                * on-link on the interface.
-                */
-               lck_mtx_lock(nd6_mutex);
-               opr = nd_prefix.lh_first;
-               while (opr) {
-                       /* does not apply in the Scoped Routing case */
-                       if (ip6_doscopedroute)
-                               break;
-
-                       if (opr == pr) {
-                               opr = opr->ndpr_next;
-                               continue;
-                       }
-
-                       NDPR_LOCK(opr);
-                       if ((opr->ndpr_stateflags & NDPRF_ONLINK) != 0) {
-                               NDPR_UNLOCK(opr);
-                               opr = opr->ndpr_next;
-                               continue;
-                       }
-                       /*
-                        * KAME specific: detached prefixes should not be
-                        * on-link.
-                        */
-                       if ((opr->ndpr_stateflags & NDPRF_DETACHED) != 0) {
-                               NDPR_UNLOCK(opr);
-                               opr = opr->ndpr_next;
-                               continue;
-                       }
-                       if (opr->ndpr_plen == plen &&
-                           in6_are_prefix_equal(&prefix.sin6_addr,
-                           &opr->ndpr_prefix.sin6_addr, plen)) {
-                               int e;
-
-                               NDPR_UNLOCK(opr);
-                               lck_mtx_unlock(nd6_mutex);
-                               if ((e = nd6_prefix_onlink(opr)) != 0) {
-                                       nd6log((LOG_ERR,
-                                           "nd6_prefix_offlink: failed to "
-                                           "recover a prefix %s/%d from %s "
-                                           "to %s (errno = %d)\n",
-                                           ip6_sprintf(&opr->ndpr_prefix.sin6_addr),
-                                           opr->ndpr_plen, if_name(ifp),
-                                           if_name(opr->ndpr_ifp), e));
-                               }
-                               lck_mtx_lock(nd6_mutex);
-                               opr = nd_prefix.lh_first;
-                       } else {
-                               NDPR_UNLOCK(opr);
-                               opr = opr->ndpr_next;
-                       }
-               }
-               lck_mtx_unlock(nd6_mutex);
        } else {
                nd6log((LOG_ERR,
                    "nd6_prefix_offlink: failed to delete route: "
@@ -3506,174 +3498,239 @@ nd6_prefix_offlink(struct nd_prefix *pr)
                    (ifscope != IFSCOPE_NONE), error));
        }
 
+       if (ndpr_rt != NULL)
+               rtfree(ndpr_rt);
+
+       lck_mtx_lock(&proxy6_lock);
+
        NDPR_LOCK(pr);
-       pr->ndpr_stateflags &= ~(NDPRF_ONLINK | NDPRF_IFSCOPE);
+       prproxy = (pr->ndpr_stateflags & NDPRF_PRPROXY);
+       VERIFY(!prproxy || !(pr->ndpr_stateflags & NDPRF_IFSCOPE));
+       pr->ndpr_stateflags &= ~(NDPRF_ONLINK | NDPRF_IFSCOPE | NDPRF_PRPROXY);
+       if (pr->ndpr_prproxy_sols_cnt > 0) {
+               VERIFY(prproxy);
+               nd6_prproxy_sols_reap(pr);
+               VERIFY(pr->ndpr_prproxy_sols_cnt == 0);
+               VERIFY(RB_EMPTY(&pr->ndpr_prproxy_sols));
+       }
        NDPR_UNLOCK(pr);
 
+       /*
+        * If this was an upstream prefix, find the downstream ones and do
+        * some cleanups.  If this was a downstream prefix, the prefix route
+        * has been removed from the routing table above, but there may be
+        * other tasks to perform.
+        */
+       nd6_prproxy_prelist_update(pr, prproxy ? pr : NULL);
+
+       NDPR_REMREF(pr);        /* release reference for this routine */
+       lck_mtx_unlock(&proxy6_lock);
+
        return (error);
 }
 
 static struct in6_ifaddr *
-in6_ifadd(
-       struct nd_prefix *pr,
-       int mcast)
+in6_pfx_newpersistaddr(struct nd_prefix *pr, int mcast, int *errorp)
 {
-       struct ifnet *ifp = pr->ndpr_ifp;
-       struct in6_aliasreq ifra;
-       struct in6_ifaddr *ia, *ib;
-       int error, plen0;
-       int updateflags;
+       struct in6_ifaddr *ia6 = NULL;
+       struct ifnet *ifp = NULL;
+       struct nd_ifinfo *ndi = NULL;
        struct in6_addr mask;
-       int prefixlen;
+       struct in6_aliasreq ifra;
+       int error, ifaupdate, iidlen, notcga;
 
-       /*
-        * find a link-local address (will be interface ID).
-        * Is it really mandatory? Theoretically, a global or a site-local
-        * address can be configured without a link-local address, if we
-        * have a unique interface identifier...
-        *
-        * it is not mandatory to have a link-local address, we can generate
-        * interface identifier on the fly.  we do this because:
-        * (1) it should be the easiest way to find interface identifier.
-        * (2) RFC2462 5.4 suggesting the use of the same interface identifier
-        * for multiple addresses on a single interface, and possible shortcut
-        * of DAD.  we omitted DAD for this reason in the past.
-        * (3) a user can prevent autoconfiguration of global address
-        * by removing link-local address by hand (this is partly because we
-        * don't have other way to control the use of IPv6 on an interface.
-        * this has been our design choice - cf. NRL's "ifconfig auto").
-        * (4) it is easier to manage when an interface has addresses
-        * with the same interface identifier, than to have multiple addresses
-        * with different interface identifiers.
-        */
-       ib = in6ifa_ifpforlinklocal(ifp, 0);/* 0 is OK? */
-       if (ib == NULL)
-               return (NULL);
+       VERIFY(pr != NULL);
+       VERIFY(errorp != NULL);
 
-       IFA_LOCK(&ib->ia_ifa);
        NDPR_LOCK(pr);
-       prefixlen = pr->ndpr_plen;
-       in6_len2mask(&mask, prefixlen);
-       plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL);
-       /* prefixlen + ifidlen must be equal to 128 */
-       if (prefixlen != plen0) {
-               nd6log((LOG_INFO, "in6_ifadd: wrong prefixlen for %s "
-                   "(prefix=%d ifid=%d)\n",
-                   if_name(ifp), prefixlen, 128 - plen0));
-               NDPR_UNLOCK(pr);
-               IFA_UNLOCK(&ib->ia_ifa);
-               IFA_REMREF(&ib->ia_ifa);
-               return (NULL);
-       }
+       ifp = pr->ndpr_ifp;
+       ia6 = NULL;
+       error = 0;
 
-       /* make ifaddr */
-
-       bzero(&ifra, sizeof(ifra));
        /*
-        * in6_update_ifa() does not use ifra_name, but we accurately set it
-        * for safety.
+        * Prefix Length check:
+        * If the sum of the prefix length and interface identifier
+        * length does not equal 128 bits, the Prefix Information
+        * option MUST be ignored.  The length of the interface
+        * identifier is defined in a separate link-type specific
+        * document.
         */
-       strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name));
+       iidlen = in6_if2idlen(ifp);
+       if (iidlen < 0) {
+               error = EADDRNOTAVAIL;
+               /* this should not happen, so we always log it. */
+               log(LOG_ERR, "%s: IID length undefined (%s)\n",
+                   __func__, if_name(ifp));
+               goto unlock1;
+       } else if (iidlen != 64) {
+               error = EADDRNOTAVAIL;
+               /*
+                * stateless autoconfiguration not yet well-defined for IID
+                * lengths other than 64 octets. Just give up for now.
+                */
+               nd6log((LOG_INFO, "%s: IID length not 64 octets (%s)\n",
+                   __func__, if_name(ifp)));
+               goto unlock1;
+       }
+
+       if (iidlen + pr->ndpr_plen != 128) {
+               error = EADDRNOTAVAIL;
+               nd6log((LOG_INFO,
+                   "%s: invalid prefix length %d for %s, ignored\n",
+                   __func__, pr->ndpr_plen, if_name(ifp)));
+               goto unlock1;
+       }
+
+       bzero(&ifra, sizeof (ifra));
+       strlcpy(ifra.ifra_name, if_name(ifp), sizeof (ifra.ifra_name));
        ifra.ifra_addr.sin6_family = AF_INET6;
-       ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
+       ifra.ifra_addr.sin6_len = sizeof (struct sockaddr_in6);
+
        /* prefix */
        bcopy(&pr->ndpr_prefix.sin6_addr, &ifra.ifra_addr.sin6_addr,
-             sizeof(ifra.ifra_addr.sin6_addr));
+           sizeof (ifra.ifra_addr.sin6_addr));
+       in6_len2mask(&mask, pr->ndpr_plen);
        ifra.ifra_addr.sin6_addr.s6_addr32[0] &= mask.s6_addr32[0];
        ifra.ifra_addr.sin6_addr.s6_addr32[1] &= mask.s6_addr32[1];
        ifra.ifra_addr.sin6_addr.s6_addr32[2] &= mask.s6_addr32[2];
        ifra.ifra_addr.sin6_addr.s6_addr32[3] &= mask.s6_addr32[3];
 
-       /* interface ID */
-       ifra.ifra_addr.sin6_addr.s6_addr32[0] |=
-           (ib->ia_addr.sin6_addr.s6_addr32[0] & ~mask.s6_addr32[0]);
-       ifra.ifra_addr.sin6_addr.s6_addr32[1] |=
-           (ib->ia_addr.sin6_addr.s6_addr32[1] & ~mask.s6_addr32[1]);
-       ifra.ifra_addr.sin6_addr.s6_addr32[2] |=
-           (ib->ia_addr.sin6_addr.s6_addr32[2] & ~mask.s6_addr32[2]);
-       ifra.ifra_addr.sin6_addr.s6_addr32[3] |=
-           (ib->ia_addr.sin6_addr.s6_addr32[3] & ~mask.s6_addr32[3]);
+       ndi = ND_IFINFO(ifp);
+       VERIFY(ndi->initialized);
+       lck_mtx_lock(&ndi->lock);
+
+       notcga = nd6_send_opstate == ND6_SEND_OPMODE_DISABLED ||
+           (ndi->flags & ND6_IFF_INSECURE) != 0;
+
+       lck_mtx_unlock(&ndi->lock);
+       NDPR_UNLOCK(pr);
+
+       if (notcga) {
+               ia6 = in6ifa_ifpforlinklocal(ifp, 0);
+               if (ia6 == NULL) {
+                       error = EADDRNOTAVAIL;
+                       nd6log((LOG_INFO, "%s: no link-local address (%s)\n",
+                           __func__, if_name(ifp)));
+                       goto done;
+               }
+
+               IFA_LOCK(&ia6->ia_ifa);
+               ifra.ifra_addr.sin6_addr.s6_addr32[0] |=
+                   (ia6->ia_addr.sin6_addr.s6_addr32[0] & ~mask.s6_addr32[0]);
+               ifra.ifra_addr.sin6_addr.s6_addr32[1] |=
+                   (ia6->ia_addr.sin6_addr.s6_addr32[1] & ~mask.s6_addr32[1]);
+               ifra.ifra_addr.sin6_addr.s6_addr32[2] |=
+                   (ia6->ia_addr.sin6_addr.s6_addr32[2] & ~mask.s6_addr32[2]);
+               ifra.ifra_addr.sin6_addr.s6_addr32[3] |=
+                   (ia6->ia_addr.sin6_addr.s6_addr32[3] & ~mask.s6_addr32[3]);
+               IFA_UNLOCK(&ia6->ia_ifa);
+               IFA_REMREF(&ia6->ia_ifa);
+               ia6 = NULL;
+       } else {
+               in6_cga_node_lock();
+               struct in6_cga_prepare local_cga_prepare;
+
+               if (ndi->cga_initialized) {
+                       bcopy(&(ndi->local_cga_modifier),
+                           &(local_cga_prepare.cga_modifier),
+                           sizeof(local_cga_prepare.cga_modifier));
+                       error = in6_cga_generate(&local_cga_prepare, 0,
+                           &ifra.ifra_addr.sin6_addr);
+               } else {
+                       error = in6_cga_generate(NULL, 0,
+                           &ifra.ifra_addr.sin6_addr);
+               }
+               in6_cga_node_unlock();
+               if (error == 0)
+                       ifra.ifra_flags |= IN6_IFF_SECURED;
+               else {
+                       nd6log((LOG_ERR, "%s: no CGA available (%s)\n",
+                           __func__, if_name(ifp)));
+                       goto done;
+               }
+       }
+
+       VERIFY(ia6 == NULL);
 
        /* new prefix mask. */
-       ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
+       ifra.ifra_prefixmask.sin6_len = sizeof (struct sockaddr_in6);
        ifra.ifra_prefixmask.sin6_family = AF_INET6;
        bcopy(&mask, &ifra.ifra_prefixmask.sin6_addr,
-             sizeof(ifra.ifra_prefixmask.sin6_addr));
+           sizeof (ifra.ifra_prefixmask.sin6_addr));
 
        /* lifetimes. */
        ifra.ifra_lifetime.ia6t_vltime = pr->ndpr_vltime;
        ifra.ifra_lifetime.ia6t_pltime = pr->ndpr_pltime;
 
-       /* XXX: scope zone ID? */
-
+       /* address flags */
        ifra.ifra_flags |= IN6_IFF_AUTOCONF; /* obey autoconf */
 
-       NDPR_UNLOCK(pr);
-       IFA_UNLOCK(&ib->ia_ifa);
-       IFA_REMREF(&ib->ia_ifa);
-
        /*
         * Make sure that we do not have this address already.  This should
         * usually not happen, but we can still see this case, e.g., if we
         * have manually configured the exact address to be configured.
         */
-       if ((ib = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr)) != NULL) {
-               IFA_REMREF(&ib->ia_ifa);
+       if ((ia6 = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr))
+           != NULL) {
+               error = EADDRNOTAVAIL;
+               IFA_REMREF(&ia6->ia_ifa);
+               ia6 = NULL;
+
                /* this should be rare enough to make an explicit log */
-               log(LOG_INFO, "in6_ifadd: %s is already configured\n",
-                   ip6_sprintf(&ifra.ifra_addr.sin6_addr));
-               return (NULL);
+               log(LOG_INFO, "%s: %s is already configured!\n",
+                   __func__, ip6_sprintf(&ifra.ifra_addr.sin6_addr));
+               goto done;
        }
 
        /*
         * Allocate ifaddr structure, link into chain, etc.
         * If we are going to create a new address upon receiving a multicasted
         * RA, we need to impose a random delay before starting DAD.
-        * [draft-ietf-ipv6-rfc2462bis-02.txt, Section 5.4.2]
+        * [RFC 4862, Section 5.4.2]
         */
-       updateflags = 0;
+       ifaupdate = IN6_IFAUPDATE_NOWAIT;
        if (mcast)
-               updateflags |= IN6_IFAUPDATE_DADDELAY;
-       error = in6_update_ifa(ifp, &ifra, NULL, updateflags, M_WAITOK);
+               ifaupdate |= IN6_IFAUPDATE_DADDELAY;
+       error = in6_update_ifa(ifp, &ifra, ifaupdate, &ia6);
        if (error != 0) {
                nd6log((LOG_ERR,
-                   "in6_ifadd: failed to make ifaddr %s on %s (errno=%d)\n",
-                   ip6_sprintf(&ifra.ifra_addr.sin6_addr), if_name(ifp),
-                   error));
-               return(NULL);   /* ifaddr must not have been allocated. */
+                   "%s: failed to make ifaddr %s on %s (errno=%d)\n",
+                   __func__, ip6_sprintf(&ifra.ifra_addr.sin6_addr),
+                   if_name(ifp), error));
+               error = EADDRNOTAVAIL;
+               goto done;
        }
 
-       ia = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr);
+       VERIFY(ia6 != NULL);
+       in6_post_msg(ifp, KEV_INET6_NEW_RTADV_ADDR, ia6, NULL);
+       goto done;
 
-       in6_post_msg(ifp, KEV_INET6_NEW_RTADV_ADDR, ia); 
+unlock1:
+       NDPR_UNLOCK(pr);
 
-       return(ia);             /* this must NOT be NULL. */
+done:
+       *errorp = error;
+       return (ia6);
 }
 
 #define        IA6_NONCONST(i) ((struct in6_ifaddr *)(uintptr_t)(i))
 
 int
-in6_tmpifadd(
-       const struct in6_ifaddr *ia0, /* corresponding public address */
-       int forcegen,
-       int how)
+in6_tmpifadd(const struct in6_ifaddr *ia0, int forcegen)
 {
        struct ifnet *ifp = ia0->ia_ifa.ifa_ifp;
        struct in6_ifaddr *ia, *newia;
        struct in6_aliasreq ifra;
-       int i, error;
+       int i, error, ifaupdate;
        int trylimit = 3;       /* XXX: adhoc value */
-       int updateflags;
        u_int32_t randid[2];
        time_t vltime0, pltime0;
-       struct timeval timenow;
+       uint64_t timenow = net_uptime();
        struct in6_addr addr;
        struct nd_prefix *ndpr;
 
-       getmicrotime(&timenow);
-
-       bzero(&ifra, sizeof(ifra));
-       strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name));
+       bzero(&ifra, sizeof (ifra));
+       strlcpy(ifra.ifra_name, if_name(ifp), sizeof (ifra.ifra_name));
        IFA_LOCK(&IA6_NONCONST(ia0)->ia_ifa);
        ifra.ifra_addr = ia0->ia_addr;
        /* copy prefix mask */
@@ -3687,7 +3744,7 @@ in6_tmpifadd(
        IFA_UNLOCK(&IA6_NONCONST(ia0)->ia_ifa);
 
 again:
-       in6_get_tmpifid(ifp, (u_int8_t *)randid,
+       in6_iid_mktmp(ifp, (u_int8_t *)randid,
            (const u_int8_t *)&addr.s6_addr[8], forcegen);
 
        ifra.ifra_addr.sin6_addr.s6_addr32[2] |=
@@ -3696,7 +3753,7 @@ again:
            (randid[1] & ~(ifra.ifra_prefixmask.sin6_addr.s6_addr32[3]));
 
        /*
-        * in6_get_tmpifid() quite likely provided a unique interface ID.
+        * in6_iid_mktmp() quite likely provided a unique interface ID.
         * However, we may still have a chance to see collision, because
         * there may be a time lag between generation of the ID and generation
         * of the address.  So, we'll do one more sanity check.
@@ -3706,7 +3763,7 @@ again:
                if (trylimit-- == 0) {
                        nd6log((LOG_NOTICE, "in6_tmpifadd: failed to find "
                            "a unique random IFID\n"));
-                       return(EEXIST);
+                       return (EEXIST);
                }
                forcegen = 1;
                goto again;
@@ -3714,24 +3771,31 @@ again:
 
        /*
         * The Valid Lifetime is the lower of the Valid Lifetime of the
-         * public address or TEMP_VALID_LIFETIME.
+        * public address or TEMP_VALID_LIFETIME.
         * The Preferred Lifetime is the lower of the Preferred Lifetime
-         * of the public address or TEMP_PREFERRED_LIFETIME -
-         * DESYNC_FACTOR.
+        * of the public address or TEMP_PREFERRED_LIFETIME -
+        * DESYNC_FACTOR.
         */
        IFA_LOCK(&IA6_NONCONST(ia0)->ia_ifa);
-       vltime0 = IFA6_IS_INVALID(ia0)
-           ? 0
-           : (ia0->ia6_lifetime.ia6t_vltime -
-             (timenow.tv_sec - ia0->ia6_updatetime));
-       if (vltime0 > ip6_temp_valid_lifetime)
+       if (ia0->ia6_lifetime.ia6ti_vltime != ND6_INFINITE_LIFETIME) {
+               vltime0 = IFA6_IS_INVALID(ia0, timenow) ? 0 :
+                   (ia0->ia6_lifetime.ia6ti_vltime -
+                   (timenow - ia0->ia6_updatetime));
+               if (vltime0 > ip6_temp_valid_lifetime)
+                       vltime0 = ip6_temp_valid_lifetime;
+       } else {
                vltime0 = ip6_temp_valid_lifetime;
-       pltime0 = IFA6_IS_DEPRECATED(ia0)
-           ? 0
-           : (ia0->ia6_lifetime.ia6t_pltime -
-             (timenow.tv_sec - ia0->ia6_updatetime));
-       if (pltime0 > ip6_temp_preferred_lifetime - ip6_desync_factor)
+       }
+       if (ia0->ia6_lifetime.ia6ti_pltime != ND6_INFINITE_LIFETIME) {
+               pltime0 = IFA6_IS_DEPRECATED(ia0, timenow) ? 0 :
+                   (ia0->ia6_lifetime.ia6ti_pltime -
+                   (timenow - ia0->ia6_updatetime));
+               if (pltime0 > ip6_temp_preferred_lifetime - ip6_desync_factor)
+                       pltime0 = ip6_temp_preferred_lifetime -
+                           ip6_desync_factor;
+       } else {
                pltime0 = ip6_temp_preferred_lifetime - ip6_desync_factor;
+       }
        ifra.ifra_lifetime.ia6t_vltime = vltime0;
        ifra.ifra_lifetime.ia6t_pltime = pltime0;
        IFA_UNLOCK(&IA6_NONCONST(ia0)->ia_ifa);
@@ -3740,28 +3804,21 @@ again:
         * Lifetime is greater than REGEN_ADVANCE time units.
         */
        if (ifra.ifra_lifetime.ia6t_pltime <= ip6_temp_regen_advance)
-               return(0);
+               return (0);
 
        /* XXX: scope zone ID? */
 
        ifra.ifra_flags |= (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY);
 
        /* allocate ifaddr structure, link into chain, etc. */
-       updateflags = 0;
-
-       if (how)
-               updateflags |= IN6_IFAUPDATE_DADDELAY;
-
-       if ((error = in6_update_ifa(ifp, &ifra, NULL, updateflags, how)) != 0)
+       ifaupdate = IN6_IFAUPDATE_NOWAIT | IN6_IFAUPDATE_DADDELAY;
+       error = in6_update_ifa(ifp, &ifra, ifaupdate, &newia);
+       if (error != 0) {
+               nd6log((LOG_ERR, "in6_tmpifadd: failed to add address.\n"));
                return (error);
-
-       newia = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr);
-       if (newia == NULL) {    /* XXX: can it happen? */
-               nd6log((LOG_ERR,
-                   "in6_tmpifadd: ifa update succeeded, but we got "
-                   "no ifaddr\n"));
-               return(EINVAL); /* XXX */
        }
+       VERIFY(newia != NULL);
+
        IFA_LOCK(&IA6_NONCONST(ia0)->ia_ifa);
        ndpr = ia0->ia6_ndpr;
        if (ndpr == NULL) {
@@ -3809,19 +3866,23 @@ again:
        /* remove our reference */
        NDPR_REMREF(ndpr);
 
-       return(0);
+       return (0);
 }
 #undef IA6_NONCONST
 
 int
 in6_init_prefix_ltimes(struct nd_prefix *ndpr)
 {
-       struct timeval timenow;
+       struct timeval caltime;
+       u_int64_t timenow = net_uptime();
 
        NDPR_LOCK_ASSERT_HELD(ndpr);
 
-       getmicrotime(&timenow);
-       /* check if preferred lifetime > valid lifetime.  RFC2462 5.5.3 (c) */
+       getmicrotime(&caltime);
+       ndpr->ndpr_base_calendartime = caltime.tv_sec;
+       ndpr->ndpr_base_uptime = timenow;
+
+       /* check if preferred lifetime > valid lifetime.  RFC 4862 5.5.3 (c) */
        if (ndpr->ndpr_pltime > ndpr->ndpr_vltime) {
                nd6log((LOG_INFO, "in6_init_prefix_ltimes: preferred lifetime"
                    "(%d) is greater than valid lifetime(%d)\n",
@@ -3831,36 +3892,35 @@ in6_init_prefix_ltimes(struct nd_prefix *ndpr)
        if (ndpr->ndpr_pltime == ND6_INFINITE_LIFETIME)
                ndpr->ndpr_preferred = 0;
        else
-               ndpr->ndpr_preferred = timenow.tv_sec + ndpr->ndpr_pltime;
+               ndpr->ndpr_preferred = timenow + ndpr->ndpr_pltime;
        if (ndpr->ndpr_vltime == ND6_INFINITE_LIFETIME)
                ndpr->ndpr_expire = 0;
        else
-               ndpr->ndpr_expire = timenow.tv_sec + ndpr->ndpr_vltime;
+               ndpr->ndpr_expire = timenow + ndpr->ndpr_vltime;
 
-       return 0;
+       return (0);
 }
 
 static void
-in6_init_address_ltimes(__unused struct nd_prefix *new,
-    struct in6_addrlifetime *lt6, boolean_t is_temporary)
+in6_init_address_ltimes(struct nd_prefix *new, struct in6_addrlifetime *lt6)
 {
-       struct timeval timenow;
+#pragma unused(new)
+       uint64_t timenow = net_uptime();
 
-       getmicrotime(&timenow);
        /* Valid lifetime must not be updated unless explicitly specified. */
        /* init ia6t_expire */
-       if (!is_temporary && lt6->ia6t_vltime == ND6_INFINITE_LIFETIME)
+       if (lt6->ia6t_vltime == ND6_INFINITE_LIFETIME) {
                lt6->ia6t_expire = 0;
-       else {
-               lt6->ia6t_expire = timenow.tv_sec;
+       else {
+               lt6->ia6t_expire = timenow;
                lt6->ia6t_expire += lt6->ia6t_vltime;
        }
 
        /* init ia6t_preferred */
-       if (!is_temporary && lt6->ia6t_pltime == ND6_INFINITE_LIFETIME)
+       if (lt6->ia6t_pltime == ND6_INFINITE_LIFETIME) {
                lt6->ia6t_preferred = 0;
-       else {
-               lt6->ia6t_preferred = timenow.tv_sec;
+       else {
+               lt6->ia6t_preferred = timenow;
                lt6->ia6t_preferred += lt6->ia6t_pltime;
        }
 }
@@ -3894,7 +3954,6 @@ rt6_deleteroute(
        struct radix_node *rn,
        void *arg)
 {
-#define SIN6(s)        ((struct sockaddr_in6 *)s)
        struct rtentry *rt = (struct rtentry *)rn;
        struct in6_addr *gate = (struct in6_addr *)arg;
 
@@ -3903,12 +3962,12 @@ rt6_deleteroute(
        RT_LOCK(rt);
        if (rt->rt_gateway == NULL || rt->rt_gateway->sa_family != AF_INET6) {
                RT_UNLOCK(rt);
-               return(0);
+               return (0);
        }
 
        if (!IN6_ARE_ADDR_EQUAL(gate, &SIN6(rt->rt_gateway)->sin6_addr)) {
                RT_UNLOCK(rt);
-               return(0);
+               return (0);
        }
        /*
         * Do not delete a static route.
@@ -3917,7 +3976,7 @@ rt6_deleteroute(
         */
        if ((rt->rt_flags & RTF_STATIC) != 0) {
                RT_UNLOCK(rt);
-               return(0);
+               return (0);
        }
        /*
         * We delete only host route. This means, in particular, we don't
@@ -3925,7 +3984,7 @@ rt6_deleteroute(
         */
        if ((rt->rt_flags & RTF_HOST) == 0) {
                RT_UNLOCK(rt);
-               return(0);
+               return (0);
        }
 
        /*
@@ -3936,7 +3995,6 @@ rt6_deleteroute(
        RT_UNLOCK(rt);
        return (rtrequest_locked(RTM_DELETE, rt_key(rt), rt->rt_gateway,
            rt_mask(rt), rt->rt_flags, 0));
-#undef SIN6
 }
 
 int
@@ -3945,13 +4003,13 @@ nd6_setdefaultiface(
 {
        int error = 0;
        ifnet_t def_ifp = NULL;
-       
+
        lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_NOTOWNED);
 
        ifnet_head_lock_shared();
        if (ifindex < 0 || if_index < ifindex) {
                ifnet_head_done();
-               return(EINVAL);
+               return (EINVAL);
        }
        def_ifp = ifindex2ifnet[ifindex];
        ifnet_head_done();
@@ -3981,19 +4039,22 @@ nd6_setdefaultiface(
                 * we do this here to avoid re-install the default route
                 * if the list is NOT empty.
                 */
-               if (ip6_doscopedroute || TAILQ_FIRST(&nd_defrouter) == NULL) {
-                       defrtrlist_sync(nd6_defifp);
+               if (odef_ifp != NULL) {
+                       defrouter_select(odef_ifp);
+               }
+
+               if (nd6_defifp != NULL) {
+                       defrouter_select(nd6_defifp);
                        nd6_prefix_sync(nd6_defifp);
                }
 
                /*
-                * Our current implementation assumes one-to-one maping between
+                * Our current implementation assumes one-to-one mapping between
                 * interfaces and links, so it would be natural to use the
                 * default interface as the default link.
                 */
                scope6_setdefault(nd6_defifp);
        }
        lck_mtx_unlock(nd6_mutex);
-
-       return(error);
+       return (error);
 }