+ /*
+ * Any failures from this point on must take into account
+ * a non-NULL "ia" with an outstanding reference count, and
+ * therefore requires IFA_REMREF. Jump to "done" label
+ * instead of calling return if "ia" is valid.
+ */
+ ia = in6ifa_ifpwithaddr(ifp, &sa6->sin6_addr);
+ }
+
+ /*
+ * SIOCDIFADDR_IN6/SIOCAIFADDR_IN6 specific tests.
+ */
+ switch (cmd) {
+ case SIOCDIFADDR_IN6: /* struct in6_ifreq */
+ if (ia == NULL)
+ return (EADDRNOTAVAIL);
+ /* FALLTHROUGH */
+ case SIOCAIFADDR_IN6_32: /* struct in6_aliasreq_32 */
+ case SIOCAIFADDR_IN6_64: /* struct in6_aliasreq_64 */
+ VERIFY(sa6 != NULL);
+ /*
+ * We always require users to specify a valid IPv6 address for
+ * the corresponding operation. Use "sa6" instead of "ifra"
+ * since SIOCDIFADDR_IN6 falls thru above.
+ */
+ if (sa6->sin6_family != AF_INET6 ||
+ sa6->sin6_len != sizeof (struct sockaddr_in6)) {
+ error = EAFNOSUPPORT;
+ goto done;
+ }
+ break;
+ }
+
+ /*
+ * Unlock the socket since ifnet_ioctl() may be invoked by
+ * one of the ioctl handlers below. Socket will be re-locked
+ * prior to returning.
+ */
+ if (so != NULL) {
+ socket_unlock(so, 0);
+ so_unlocked = TRUE;
+ }
+
+ /*
+ * And finally process address-related ioctls.
+ */
+ switch (cmd) {
+ case SIOCGIFADDR_IN6: /* struct in6_ifreq */
+ /* This interface is basically deprecated. use SIOCGIFCONF. */
+ /* FALLTHRU */
+ case SIOCGIFDSTADDR_IN6: /* struct in6_ifreq */
+ error = in6ctl_gifaddr(ifp, ia, cmd, ifr);
+ break;
+
+ case SIOCGIFNETMASK_IN6: /* struct in6_ifreq */
+ if (ia != NULL) {
+ IFA_LOCK(&ia->ia_ifa);
+ bcopy(&ia->ia_prefixmask, &ifr->ifr_addr,
+ sizeof (struct sockaddr_in6));
+ IFA_UNLOCK(&ia->ia_ifa);
+ } else {
+ error = EADDRNOTAVAIL;
+ }
+ break;
+
+ case SIOCGIFAFLAG_IN6: /* struct in6_ifreq */
+ if (ia != NULL) {
+ IFA_LOCK(&ia->ia_ifa);
+ bcopy(&ia->ia6_flags, &ifr->ifr_ifru.ifru_flags6,
+ sizeof (ifr->ifr_ifru.ifru_flags6));
+ IFA_UNLOCK(&ia->ia_ifa);
+ } else {
+ error = EADDRNOTAVAIL;
+ }
+ break;
+
+ case SIOCGIFALIFETIME_IN6: /* struct in6_ifreq */
+ case SIOCSIFALIFETIME_IN6: /* struct in6_ifreq */
+ error = in6ctl_alifetime(ia, cmd, ifr, p64);
+ break;
+
+ case SIOCAIFADDR_IN6_32: /* struct in6_aliasreq_32 */
+ case SIOCAIFADDR_IN6_64: /* struct in6_aliasreq_64 */
+ error = in6ctl_aifaddr(ifp, ifra);
+ break;
+
+ case SIOCDIFADDR_IN6:
+ in6ctl_difaddr(ifp, ia);
+ break;
+
+ default:
+ error = ifnet_ioctl(ifp, PF_INET6, cmd, data);
+ break;
+ }
+
+done:
+ if (ia != NULL)
+ IFA_REMREF(&ia->ia_ifa);
+ if (so_unlocked)
+ socket_lock(so, 0);
+
+ return (error);
+}
+
+static __attribute__((noinline)) int
+in6ctl_aifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra)
+{
+ int i, error, addtmp, plen;
+ struct nd_prefix pr0, *pr;
+ struct in6_ifaddr *ia;
+
+ VERIFY(ifp != NULL && ifra != NULL);
+ ia = NULL;
+
+ /* Attempt to attach the protocol, in case it isn't attached */
+ error = in6_domifattach(ifp);
+ if (error == 0) {
+ /* PF_INET6 wasn't previously attached */
+ error = in6_ifattach_aliasreq(ifp, NULL, NULL);
+ if (error != 0)
+ goto done;
+
+ in6_if_up_dad_start(ifp);
+ } else if (error != EEXIST) {
+ goto done;
+ }
+
+ /*
+ * First, make or update the interface address structure, and link it
+ * to the list.
+ */
+ error = in6_update_ifa(ifp, ifra, 0, &ia);
+ if (error != 0)
+ goto done;
+ VERIFY(ia != NULL);
+
+ /* Now, make the prefix on-link on the interface. */
+ plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr, NULL);
+ if (plen == 128)
+ goto done;
+
+ /*
+ * NOTE: We'd rather create the prefix before the address, but we need
+ * at least one address to install the corresponding interface route,
+ * so we configure the address first.
+ */
+
+ /*
+ * Convert mask to prefix length (prefixmask has already been validated
+ * in in6_update_ifa().
+ */
+ bzero(&pr0, sizeof (pr0));
+ pr0.ndpr_plen = plen;
+ pr0.ndpr_ifp = ifp;
+ pr0.ndpr_prefix = ifra->ifra_addr;
+ pr0.ndpr_mask = ifra->ifra_prefixmask.sin6_addr;
+
+ /* apply the mask for safety. */
+ for (i = 0; i < 4; i++) {
+ pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &=
+ ifra->ifra_prefixmask.sin6_addr.s6_addr32[i];
+ }
+
+ /*
+ * Since we don't have an API to set prefix (not address) lifetimes, we
+ * just use the same lifetimes as addresses. The (temporarily)
+ * installed lifetimes can be overridden by later advertised RAs (when
+ * accept_rtadv is non 0), which is an intended behavior.
+ */
+ pr0.ndpr_raf_onlink = 1; /* should be configurable? */
+ pr0.ndpr_raf_auto = !!(ifra->ifra_flags & IN6_IFF_AUTOCONF);
+ pr0.ndpr_vltime = ifra->ifra_lifetime.ia6t_vltime;
+ pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime;
+ pr0.ndpr_stateflags |= NDPRF_STATIC;
+ lck_mtx_init(&pr0.ndpr_lock, ifa_mtx_grp, ifa_mtx_attr);
+
+ /* add the prefix if there's one. */
+ if ((pr = nd6_prefix_lookup(&pr0)) == NULL) {
+ /*
+ * nd6_prelist_add will install the corresponding interface
+ * route.
+ */
+ error = nd6_prelist_add(&pr0, NULL, &pr, FALSE);
+ if (error != 0)
+ goto done;
+
+ if (pr == NULL) {
+ log(LOG_ERR, "%s: nd6_prelist_add okay, but"
+ " no prefix.\n", __func__);
+ error = EINVAL;
+ goto done;
+ }
+ }
+
+ IFA_LOCK(&ia->ia_ifa);
+
+ /* if this is a new autoconfed addr */
+ addtmp = FALSE;
+ if ((ia->ia6_flags & IN6_IFF_AUTOCONF) != 0 && ia->ia6_ndpr == NULL) {
+ NDPR_LOCK(pr);
+ ++pr->ndpr_addrcnt;
+ VERIFY(pr->ndpr_addrcnt != 0);
+ ia->ia6_ndpr = pr;
+ NDPR_ADDREF_LOCKED(pr); /* for addr reference */
+
+ /*
+ * If this is the first autoconf address from the prefix,
+ * create a temporary address as well (when specified).
+ */
+ addtmp = (ip6_use_tempaddr && pr->ndpr_addrcnt == 1);
+ NDPR_UNLOCK(pr);
+ }
+
+ IFA_UNLOCK(&ia->ia_ifa);
+
+ if (addtmp) {
+ int e;
+ e = in6_tmpifadd(ia, 1);
+ if (e != 0)
+ log(LOG_NOTICE, "%s: failed to create a"
+ " temporary address, error=%d\n",
+ __func__, e);
+ }
+
+ /*
+ * This might affect the status of autoconfigured addresses, that is,
+ * this address might make other addresses detached.
+ */
+ lck_mtx_lock(nd6_mutex);
+ pfxlist_onlink_check();
+ lck_mtx_unlock(nd6_mutex);
+
+ /* Drop use count held above during lookup/add */
+ NDPR_REMREF(pr);
+
+done:
+ if (ia != NULL)
+ IFA_REMREF(&ia->ia_ifa);
+ return (error);
+}
+
+static __attribute__((noinline)) void
+in6ctl_difaddr(struct ifnet *ifp, struct in6_ifaddr *ia)
+{
+ int i = 0;
+ struct nd_prefix pr0, *pr;
+
+ VERIFY(ifp != NULL && ia != NULL);
+
+ /*
+ * If the address being deleted is the only one that owns
+ * the corresponding prefix, expire the prefix as well.
+ * XXX: theoretically, we don't have to worry about such
+ * relationship, since we separate the address management
+ * and the prefix management. We do this, however, to provide
+ * as much backward compatibility as possible in terms of
+ * the ioctl operation.
+ * Note that in6_purgeaddr() will decrement ndpr_addrcnt.
+ */
+ IFA_LOCK(&ia->ia_ifa);
+ bzero(&pr0, sizeof (pr0));
+ pr0.ndpr_ifp = ifp;
+ pr0.ndpr_plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL);
+ if (pr0.ndpr_plen == 128) {
+ IFA_UNLOCK(&ia->ia_ifa);
+ goto purgeaddr;
+ }
+ pr0.ndpr_prefix = ia->ia_addr;
+ pr0.ndpr_mask = ia->ia_prefixmask.sin6_addr;
+ for (i = 0; i < 4; i++) {
+ pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &=
+ ia->ia_prefixmask.sin6_addr.s6_addr32[i];
+ }
+ IFA_UNLOCK(&ia->ia_ifa);
+ /*
+ * The logic of the following condition is a bit complicated.
+ * We expire the prefix when
+ * 1. the address obeys autoconfiguration and it is the
+ * only owner of the associated prefix, or
+ * 2. the address does not obey autoconf and there is no
+ * other owner of the prefix.
+ */
+ if ((pr = nd6_prefix_lookup(&pr0)) != NULL) {
+ IFA_LOCK(&ia->ia_ifa);
+ NDPR_LOCK(pr);
+ if (((ia->ia6_flags & IN6_IFF_AUTOCONF) != 0 &&
+ pr->ndpr_addrcnt == 1) ||
+ ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0 &&
+ pr->ndpr_addrcnt == 0)) {
+ /* XXX: just for expiration */
+ pr->ndpr_expire = 1;
+ }
+ NDPR_UNLOCK(pr);
+ IFA_UNLOCK(&ia->ia_ifa);
+
+ /* Drop use count held above during lookup */
+ NDPR_REMREF(pr);
+ }
+
+purgeaddr:
+ in6_purgeaddr(&ia->ia_ifa);
+}
+
+static __attribute__((noinline)) int
+in6_autoconf(struct ifnet *ifp, int enable)
+{
+ int error = 0;
+
+ VERIFY(ifp != NULL);
+
+ if (ifp->if_flags & IFF_LOOPBACK)
+ return (EINVAL);
+
+ if (enable) {
+ /*
+ * An interface in IPv6 router mode implies that it
+ * is either configured with a static IP address or
+ * autoconfigured via a locally-generated RA. Prevent
+ * SIOCAUTOCONF_START from being set in that mode.
+ */
+ ifnet_lock_exclusive(ifp);
+ if (ifp->if_eflags & IFEF_IPV6_ROUTER) {
+ ifp->if_eflags &= ~IFEF_ACCEPT_RTADV;
+ error = EBUSY;
+ } else {
+ ifp->if_eflags |= IFEF_ACCEPT_RTADV;
+ }
+ ifnet_lock_done(ifp);
+ } else {
+ struct in6_ifaddr *ia = NULL;
+
+ ifnet_lock_exclusive(ifp);
+ ifp->if_eflags &= ~IFEF_ACCEPT_RTADV;
+ ifnet_lock_done(ifp);
+
+ /* Remove autoconfigured address from interface */
+ lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
+ ia = in6_ifaddrs;
+ while (ia != NULL) {
+ if (ia->ia_ifa.ifa_ifp != ifp) {
+ ia = ia->ia_next;
+ continue;
+ }
+ IFA_LOCK(&ia->ia_ifa);
+ if (ia->ia6_flags & IN6_IFF_AUTOCONF) {
+ IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for us */
+ IFA_UNLOCK(&ia->ia_ifa);
+ lck_rw_done(&in6_ifaddr_rwlock);
+ in6_purgeaddr(&ia->ia_ifa);
+ IFA_REMREF(&ia->ia_ifa); /* for us */
+ lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
+ /*
+ * Purging the address caused in6_ifaddr_rwlock
+ * to be dropped and reacquired;
+ * therefore search again from the beginning
+ * of in6_ifaddrs list.
+ */
+ ia = in6_ifaddrs;
+ continue;
+ }
+ IFA_UNLOCK(&ia->ia_ifa);
+ ia = ia->ia_next;
+ }
+ lck_rw_done(&in6_ifaddr_rwlock);
+ }
+ return (error);
+}
+
+/*
+ * Handle SIOCSETROUTERMODE_IN6 to set or clear the IPv6 router mode flag on
+ * the interface. Entering or exiting this mode will result in the removal of
+ * autoconfigured IPv6 addresses on the interface.
+ */
+static __attribute__((noinline)) int
+in6_setrouter(struct ifnet *ifp, int enable)
+{
+ VERIFY(ifp != NULL);
+
+ if (ifp->if_flags & IFF_LOOPBACK)
+ return (ENODEV);
+
+ if (enable) {
+ struct nd_ifinfo *ndi;
+
+ lck_rw_lock_shared(nd_if_rwlock);
+ ndi = ND_IFINFO(ifp);
+ if (ndi != NULL && ndi->initialized) {
+ lck_mtx_lock(&ndi->lock);
+ if (ndi->flags & ND6_IFF_PROXY_PREFIXES) {
+ /* No proxy if we are an advertising router */
+ ndi->flags &= ~ND6_IFF_PROXY_PREFIXES;
+ lck_mtx_unlock(&ndi->lock);
+ lck_rw_done(nd_if_rwlock);
+ (void) nd6_if_prproxy(ifp, FALSE);
+ } else {
+ lck_mtx_unlock(&ndi->lock);
+ lck_rw_done(nd_if_rwlock);
+ }
+ } else {
+ lck_rw_done(nd_if_rwlock);
+ }
+ }
+
+ ifnet_lock_exclusive(ifp);
+ if (enable) {
+ ifp->if_eflags |= IFEF_IPV6_ROUTER;
+ } else {
+ ifp->if_eflags &= ~IFEF_IPV6_ROUTER;
+ }
+ ifnet_lock_done(ifp);
+
+ lck_mtx_lock(nd6_mutex);
+ defrouter_select(ifp);
+ lck_mtx_unlock(nd6_mutex);
+
+ if_allmulti(ifp, enable);
+
+ return (in6_autoconf(ifp, FALSE));
+}
+
+static int
+in6_to_kamescope(struct sockaddr_in6 *sin6, struct ifnet *ifp)
+{
+ struct sockaddr_in6 tmp;
+ int error, id;
+
+ VERIFY(sin6 != NULL);
+ tmp = *sin6;
+
+ error = in6_recoverscope(&tmp, &sin6->sin6_addr, ifp);
+ if (error != 0)
+ return (error);
+
+ id = in6_addr2scopeid(ifp, &tmp.sin6_addr);
+ if (tmp.sin6_scope_id == 0)
+ tmp.sin6_scope_id = id;
+ else if (tmp.sin6_scope_id != id)
+ return (EINVAL); /* scope ID mismatch. */
+
+ error = in6_embedscope(&tmp.sin6_addr, &tmp, NULL, NULL, NULL);
+ if (error != 0)
+ return (error);
+
+ tmp.sin6_scope_id = 0;
+ *sin6 = tmp;
+ return (0);
+}
+
+static int
+in6_ifaupdate_aux(struct in6_ifaddr *ia, struct ifnet *ifp, int ifaupflags)
+{
+ struct sockaddr_in6 mltaddr, mltmask;
+ struct in6_addr llsol;
+ struct ifaddr *ifa;
+ struct in6_multi *in6m_sol;
+ struct in6_multi_mship *imm;
+ struct rtentry *rt;
+ int delay, error;
+
+ VERIFY(ifp != NULL && ia != NULL);
+ ifa = &ia->ia_ifa;
+ in6m_sol = NULL;
+
+ /*
+ * Mark the address as tentative before joining multicast addresses,
+ * so that corresponding MLD responses would not have a tentative
+ * source address.
+ */
+ ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /* safety */
+ if (in6if_do_dad(ifp))
+ in6_ifaddr_set_dadprogress(ia);
+
+ /* Join necessary multicast groups */
+ if ((ifp->if_flags & IFF_MULTICAST) != 0) {
+
+ /* join solicited multicast addr for new host id */
+ bzero(&llsol, sizeof (struct in6_addr));
+ llsol.s6_addr32[0] = IPV6_ADDR_INT32_MLL;
+ llsol.s6_addr32[1] = 0;
+ llsol.s6_addr32[2] = htonl(1);
+ llsol.s6_addr32[3] = ia->ia_addr.sin6_addr.s6_addr32[3];
+ llsol.s6_addr8[12] = 0xff;
+ if ((error = in6_setscope(&llsol, ifp, NULL)) != 0) {
+ /* XXX: should not happen */
+ log(LOG_ERR, "%s: in6_setscope failed\n", __func__);
+ goto unwind;
+ }
+ delay = 0;
+ if ((ifaupflags & IN6_IFAUPDATE_DADDELAY)) {
+ /*
+ * We need a random delay for DAD on the address
+ * being configured. It also means delaying
+ * transmission of the corresponding MLD report to
+ * avoid report collision. [RFC 4862]
+ */
+ delay = random() % MAX_RTR_SOLICITATION_DELAY;
+ }
+ imm = in6_joingroup(ifp, &llsol, &error, delay);
+ if (imm == NULL) {
+ nd6log((LOG_WARNING,
+ "%s: addmulti failed for %s on %s (errno=%d)\n",
+ __func__, ip6_sprintf(&llsol), if_name(ifp),
+ error));
+ VERIFY(error != 0);
+ goto unwind;
+ }
+ in6m_sol = imm->i6mm_maddr;
+ /* take a refcount for this routine */
+ IN6M_ADDREF(in6m_sol);
+
+ IFA_LOCK_SPIN(ifa);
+ LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain);
+ IFA_UNLOCK(ifa);
+
+ bzero(&mltmask, sizeof (mltmask));
+ mltmask.sin6_len = sizeof (struct sockaddr_in6);
+ mltmask.sin6_family = AF_INET6;
+ mltmask.sin6_addr = in6mask32;
+#define MLTMASK_LEN 4 /* mltmask's masklen (=32bit=4octet) */
+
+ /*
+ * join link-local all-nodes address
+ */
+ bzero(&mltaddr, sizeof (mltaddr));
+ mltaddr.sin6_len = sizeof (struct sockaddr_in6);
+ mltaddr.sin6_family = AF_INET6;
+ mltaddr.sin6_addr = in6addr_linklocal_allnodes;
+ if ((error = in6_setscope(&mltaddr.sin6_addr, ifp, NULL)) != 0)
+ goto unwind; /* XXX: should not fail */
+
+ /*
+ * XXX: do we really need this automatic routes?
+ * We should probably reconsider this stuff. Most applications
+ * actually do not need the routes, since they usually specify
+ * the outgoing interface.
+ */
+ rt = rtalloc1_scoped((struct sockaddr *)&mltaddr, 0, 0UL,
+ ia->ia_ifp->if_index);
+ if (rt) {
+ if (memcmp(&mltaddr.sin6_addr, &((struct sockaddr_in6 *)
+ (void *)rt_key(rt))->sin6_addr, MLTMASK_LEN)) {
+ rtfree(rt);
+ rt = NULL;
+ }
+ }
+ if (!rt) {
+ error = rtrequest_scoped(RTM_ADD,
+ (struct sockaddr *)&mltaddr,
+ (struct sockaddr *)&ia->ia_addr,
+ (struct sockaddr *)&mltmask, RTF_UP | RTF_CLONING,
+ NULL, ia->ia_ifp->if_index);