+in6_selectsrc_core(struct sockaddr_in6 *dstsock, uint32_t hint_mask,
+ struct ifnet *ifp, int srcsel_debug, struct in6_addr *src_storage,
+ struct ifnet **sifp, int *errorp, struct ifaddr **ifapp)
+{
+ u_int32_t odstzone;
+ int bestrule = IP6S_SRCRULE_0;
+ struct in6_addrpolicy *dst_policy = NULL, *best_policy = NULL;
+ struct in6_addr dst;
+ struct in6_ifaddr *ia = NULL, *ia_best = NULL;
+ char s_src[MAX_IPv6_STR_LEN] = {0};
+ char s_dst[MAX_IPv6_STR_LEN] = {0};
+ const struct in6_addr *tmp = NULL;
+ int dst_scope = -1, best_scope = -1, best_matchlen = -1;
+ uint64_t secs = net_uptime();
+ VERIFY(dstsock != NULL);
+ VERIFY(src_storage != NULL);
+ VERIFY(ifp != NULL);
+
+ if (sifp != NULL)
+ *sifp = NULL;
+
+ if (ifapp != NULL)
+ *ifapp = NULL;
+
+ dst = dstsock->sin6_addr; /* make a copy for local operation */
+
+ if (srcsel_debug) {
+ (void) inet_ntop(AF_INET6, &dst, s_dst, sizeof (s_src));
+
+ tmp = &in6addr_any;
+ (void) inet_ntop(AF_INET6, tmp, s_src, sizeof (s_src));
+ printf("%s out src %s dst %s ifp %s\n",
+ __func__, s_src, s_dst, ifp->if_xname);
+ }
+
+ *errorp = in6_setscope(&dst, ifp, &odstzone);
+ if (*errorp != 0) {
+ src_storage = NULL;
+ goto done;
+ }
+
+ lck_rw_lock_shared(&in6_ifaddr_rwlock);
+ for (ia = in6_ifaddrs; ia; ia = ia->ia_next) {
+ int new_scope = -1, new_matchlen = -1;
+ struct in6_addrpolicy *new_policy = NULL;
+ u_int32_t srczone = 0, osrczone, dstzone;
+ struct in6_addr src;
+ struct ifnet *ifp1 = ia->ia_ifp;
+ int srcrule;
+
+ if (srcsel_debug)
+ (void) inet_ntop(AF_INET6, &ia->ia_addr.sin6_addr,
+ s_src, sizeof (s_src));
+
+ IFA_LOCK(&ia->ia_ifa);
+
+ /*
+ * XXX By default we are strong end system and will
+ * limit candidate set of source address to the ones
+ * configured on the outgoing interface.
+ */
+ if (ip6_select_src_strong_end &&
+ ifp1 != ifp) {
+ SASEL_LOG("NEXT ia %s ifp1 %s address is not on outgoing "
+ "interface \n", s_src, ifp1->if_xname);
+ goto next;
+ }
+
+ /*
+ * We'll never take an address that breaks the scope zone
+ * of the destination. We also skip an address if its zone
+ * does not contain the outgoing interface.
+ * XXX: we should probably use sin6_scope_id here.
+ */
+ if (in6_setscope(&dst, ifp1, &dstzone) ||
+ odstzone != dstzone) {
+ SASEL_LOG("NEXT ia %s ifp1 %s odstzone %d != dstzone %d\n",
+ s_src, ifp1->if_xname, odstzone, dstzone);
+ goto next;
+ }
+ src = ia->ia_addr.sin6_addr;
+ if (in6_setscope(&src, ifp, &osrczone) ||
+ in6_setscope(&src, ifp1, &srczone) ||
+ osrczone != srczone) {
+ SASEL_LOG("NEXT ia %s ifp1 %s osrczone %d != srczone %d\n",
+ s_src, ifp1->if_xname, osrczone, srczone);
+ goto next;
+ }
+ /* avoid unusable addresses */
+ if ((ia->ia6_flags &
+ (IN6_IFF_NOTREADY | IN6_IFF_ANYCAST | IN6_IFF_DETACHED))) {
+ SASEL_LOG("NEXT ia %s ifp1 %s ia6_flags 0x%x\n",
+ s_src, ifp1->if_xname, ia->ia6_flags);
+ goto next;
+ }
+ if (!ip6_use_deprecated && IFA6_IS_DEPRECATED(ia, secs)) {
+ SASEL_LOG("NEXT ia %s ifp1 %s IFA6_IS_DEPRECATED\n",
+ s_src, ifp1->if_xname);
+ goto next;
+ }
+ if (!nd6_optimistic_dad &&
+ (ia->ia6_flags & IN6_IFF_OPTIMISTIC) != 0) {
+ SASEL_LOG("NEXT ia %s ifp1 %s IN6_IFF_OPTIMISTIC\n",
+ s_src, ifp1->if_xname);
+ goto next;
+ }
+ /* Rule 1: Prefer same address */
+ if (IN6_ARE_ADDR_EQUAL(&dst, &ia->ia_addr.sin6_addr))
+ BREAK(IP6S_SRCRULE_1); /* there should be no better candidate */
+
+ if (ia_best == NULL)
+ REPLACE(IP6S_SRCRULE_0);
+
+ /* Rule 2: Prefer appropriate scope */
+ if (dst_scope < 0)
+ dst_scope = in6_addrscope(&dst);
+ new_scope = in6_addrscope(&ia->ia_addr.sin6_addr);
+ if (IN6_ARE_SCOPE_CMP(best_scope, new_scope) < 0) {
+ if (IN6_ARE_SCOPE_CMP(best_scope, dst_scope) < 0)
+ REPLACE(IP6S_SRCRULE_2);
+ NEXTSRC(IP6S_SRCRULE_2);
+ } else if (IN6_ARE_SCOPE_CMP(new_scope, best_scope) < 0) {
+ if (IN6_ARE_SCOPE_CMP(new_scope, dst_scope) < 0)
+ NEXTSRC(IP6S_SRCRULE_2);
+ REPLACE(IP6S_SRCRULE_2);
+ }
+
+ /*
+ * Rule 3: Avoid deprecated addresses. Note that the case of
+ * !ip6_use_deprecated is already rejected above.
+ */
+ if (!IFA6_IS_DEPRECATED(ia_best, secs) &&
+ IFA6_IS_DEPRECATED(ia, secs))
+ NEXTSRC(IP6S_SRCRULE_3);
+ if (IFA6_IS_DEPRECATED(ia_best, secs) &&
+ !IFA6_IS_DEPRECATED(ia, secs))
+ REPLACE(IP6S_SRCRULE_3);
+
+ /*
+ * RFC 4429 says that optimistic addresses are equivalent to
+ * deprecated addresses, so avoid them here.
+ */
+ if ((ia_best->ia6_flags & IN6_IFF_OPTIMISTIC) == 0 &&
+ (ia->ia6_flags & IN6_IFF_OPTIMISTIC) != 0)
+ NEXTSRC(IP6S_SRCRULE_3);
+ if ((ia_best->ia6_flags & IN6_IFF_OPTIMISTIC) != 0 &&
+ (ia->ia6_flags & IN6_IFF_OPTIMISTIC) == 0)
+ REPLACE(IP6S_SRCRULE_3);
+
+ /* Rule 4: Prefer home addresses */
+ /*
+ * XXX: This is a TODO. We should probably merge the MIP6
+ * case above.
+ */
+
+ /* Rule 5: Prefer outgoing interface */
+ /*
+ * XXX By default we are strong end with source address
+ * selection. That means all address selection candidate
+ * addresses will be the ones hosted on the outgoing interface
+ * making the following check redundant.
+ */
+ if (ip6_select_src_strong_end == 0) {
+ if (ia_best->ia_ifp == ifp && ia->ia_ifp != ifp)
+ NEXTSRC(IP6S_SRCRULE_5);
+ if (ia_best->ia_ifp != ifp && ia->ia_ifp == ifp)
+ REPLACE(IP6S_SRCRULE_5);
+ }
+
+ /*
+ * Rule 6: Prefer matching label
+ * Note that best_policy should be non-NULL here.
+ */
+ if (dst_policy == NULL)
+ dst_policy = in6_addrsel_lookup_policy(dstsock);
+ if (dst_policy->label != ADDR_LABEL_NOTAPP) {
+ new_policy = in6_addrsel_lookup_policy(&ia->ia_addr);
+ if (dst_policy->label == best_policy->label &&
+ dst_policy->label != new_policy->label)
+ NEXTSRC(IP6S_SRCRULE_6);
+ if (dst_policy->label != best_policy->label &&
+ dst_policy->label == new_policy->label)
+ REPLACE(IP6S_SRCRULE_6);
+ }
+
+ /*
+ * Rule 7: Prefer temporary addresses.
+ * We allow users to reverse the logic by configuring
+ * a sysctl variable, so that transparency conscious users can
+ * always prefer stable addresses.
+ */
+ if (!(ia_best->ia6_flags & IN6_IFF_TEMPORARY) &&
+ (ia->ia6_flags & IN6_IFF_TEMPORARY)) {
+ if (hint_mask & IPV6_SRCSEL_HINT_PREFER_TMPADDR)
+ REPLACE(IP6S_SRCRULE_7);
+ else
+ NEXTSRC(IP6S_SRCRULE_7);
+ }
+ if ((ia_best->ia6_flags & IN6_IFF_TEMPORARY) &&
+ !(ia->ia6_flags & IN6_IFF_TEMPORARY)) {
+ if (hint_mask & IPV6_SRCSEL_HINT_PREFER_TMPADDR)
+ NEXTSRC(IP6S_SRCRULE_7);
+ else
+ REPLACE(IP6S_SRCRULE_7);
+ }
+
+ /*
+ * Rule 7x: prefer addresses on alive interfaces.
+ * This is a KAME specific rule.
+ */
+ if ((ia_best->ia_ifp->if_flags & IFF_UP) &&
+ !(ia->ia_ifp->if_flags & IFF_UP))
+ NEXTSRC(IP6S_SRCRULE_7x);
+ if (!(ia_best->ia_ifp->if_flags & IFF_UP) &&
+ (ia->ia_ifp->if_flags & IFF_UP))
+ REPLACE(IP6S_SRCRULE_7x);
+
+ /*
+ * Rule 8: Use longest matching prefix.
+ */
+ new_matchlen = in6_matchlen(&ia->ia_addr.sin6_addr, &dst);
+ if (best_matchlen < new_matchlen)
+ REPLACE(IP6S_SRCRULE_8);
+ if (new_matchlen < best_matchlen)
+ NEXTSRC(IP6S_SRCRULE_8);
+
+ /*
+ * Last resort: just keep the current candidate.
+ * Or, do we need more rules?
+ */
+ if (ifp1 != ifp && (ifp1->if_eflags & IFEF_EXPENSIVE) &&
+ ip6_select_src_expensive_secondary_if == 0) {
+ SASEL_LOG("NEXT ia %s ifp1 %s IFEF_EXPENSIVE\n",
+ s_src, ifp1->if_xname);
+ ip6stat.ip6s_sources_skip_expensive_secondary_if++;
+ goto next;
+ }
+ SASEL_LOG("NEXT ia %s ifp1 %s last resort\n",
+ s_src, ifp1->if_xname);
+ IFA_UNLOCK(&ia->ia_ifa);
+ continue;
+
+replace:
+ /*
+ * Ignore addresses on secondary interfaces that are marked
+ * expensive
+ */
+ if (ifp1 != ifp && (ifp1->if_eflags & IFEF_EXPENSIVE) &&
+ ip6_select_src_expensive_secondary_if == 0) {
+ SASEL_LOG("NEXT ia %s ifp1 %s IFEF_EXPENSIVE\n",
+ s_src, ifp1->if_xname);
+ ip6stat.ip6s_sources_skip_expensive_secondary_if++;
+ goto next;
+ }
+ bestrule = srcrule;
+ best_scope = (new_scope >= 0 ? new_scope :
+ in6_addrscope(&ia->ia_addr.sin6_addr));
+ best_policy = (new_policy ? new_policy :
+ in6_addrsel_lookup_policy(&ia->ia_addr));
+ best_matchlen = (new_matchlen >= 0 ? new_matchlen :
+ in6_matchlen(&ia->ia_addr.sin6_addr, &dst));
+ SASEL_LOG("NEXT ia %s ifp1 %s best_scope %d new_scope %d dst_scope %d\n",
+ s_src, ifp1->if_xname, best_scope, new_scope, dst_scope);
+ IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for ia_best */
+ IFA_UNLOCK(&ia->ia_ifa);
+ if (ia_best != NULL)
+ IFA_REMREF(&ia_best->ia_ifa);
+ ia_best = ia;
+ continue;
+
+next:
+ IFA_UNLOCK(&ia->ia_ifa);
+ continue;
+
+out:
+ IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for ia_best */
+ IFA_UNLOCK(&ia->ia_ifa);
+ if (ia_best != NULL)
+ IFA_REMREF(&ia_best->ia_ifa);
+ ia_best = ia;
+ break;
+ }
+
+ lck_rw_done(&in6_ifaddr_rwlock);
+
+ if ((ia = ia_best) == NULL) {
+ if (*errorp == 0)
+ *errorp = EADDRNOTAVAIL;
+ src_storage = NULL;
+ goto done;
+ }
+
+ if (sifp != NULL) {
+ *sifp = ia->ia_ifa.ifa_ifp;
+ ifnet_reference(*sifp);
+ }
+
+ IFA_LOCK_SPIN(&ia->ia_ifa);
+ if (bestrule < IP6S_SRCRULE_COUNT)
+ ip6stat.ip6s_sources_rule[bestrule]++;
+ *src_storage = satosin6(&ia->ia_addr)->sin6_addr;
+ IFA_UNLOCK(&ia->ia_ifa);
+
+ if (ifapp != NULL)
+ *ifapp = &ia->ia_ifa;
+ else
+ IFA_REMREF(&ia->ia_ifa);
+
+done:
+ if (srcsel_debug) {
+ (void) inet_ntop(AF_INET6, &dst, s_dst, sizeof (s_src));
+
+ tmp = (src_storage != NULL) ? src_storage : &in6addr_any;
+ (void) inet_ntop(AF_INET6, tmp, s_src, sizeof (s_src));
+
+ printf("%s out src %s dst %s dst_scope %d best_scope %d\n",
+ __func__, s_src, s_dst, dst_scope, best_scope);
+ }
+
+ return (src_storage);
+}
+
+/*
+ * Regardless of error, it will return an ifp with a reference held if the
+ * caller provides a non-NULL ifpp. The caller is responsible for checking
+ * if the returned ifp is valid and release its reference at all times.
+ */
+struct in6_addr *
+in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts,
+ struct inpcb *inp, struct route_in6 *ro,
+ struct ifnet **ifpp, struct in6_addr *src_storage, unsigned int ifscope,
+ int *errorp)