X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/fe8ab488e9161c46dd9885d58fc52996dc0249ff..5ba3f43ea354af8ad55bea84372a2bc834d8757c:/bsd/netinet6/in6_src.c diff --git a/bsd/netinet6/in6_src.c b/bsd/netinet6/in6_src.c index 86b703bf5..a987f4250 100644 --- a/bsd/netinet6/in6_src.c +++ b/bsd/netinet6/in6_src.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2014 Apple Inc. All rights reserved. + * Copyright (c) 2000-2016 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -133,6 +133,21 @@ SYSCTL_INT(_net_inet6_ip6, OID_AUTO, select_srcif_debug, CTLFLAG_RW | CTLFLAG_LOCKED, &ip6_select_srcif_debug, 0, "log source interface selection debug info"); +static int ip6_select_srcaddr_debug = 0; +SYSCTL_INT(_net_inet6_ip6, OID_AUTO, select_srcaddr_debug, + CTLFLAG_RW | CTLFLAG_LOCKED, &ip6_select_srcaddr_debug, 0, + "log source address selection debug info"); + +static int ip6_select_src_expensive_secondary_if = 0; +SYSCTL_INT(_net_inet6_ip6, OID_AUTO, select_src_expensive_secondary_if, + CTLFLAG_RW | CTLFLAG_LOCKED, &ip6_select_src_expensive_secondary_if, 0, + "allow source interface selection to use expensive secondaries"); + +static int ip6_select_src_strong_end = 1; +SYSCTL_INT(_net_inet6_ip6, OID_AUTO, select_src_strong_end, + CTLFLAG_RW | CTLFLAG_LOCKED, &ip6_select_src_strong_end, 0, + "limit source address selection to outgoing interface"); + #define ADDR_LABEL_NOTAPP (-1) struct in6_addrpolicy defaultaddrpolicy; @@ -164,6 +179,18 @@ static int dump_addrsel_policyent(const struct in6_addrpolicy *, void *); static struct in6_addrpolicy *match_addrsel_policy(struct sockaddr_in6 *); void addrsel_policy_init(void); +#define SASEL_DO_DBG(inp) \ + (ip6_select_srcaddr_debug && (inp) != NULL && \ + (inp)->inp_socket != NULL && \ + ((inp)->inp_socket->so_options & SO_DEBUG)) + +#define SASEL_LOG(fmt, ...) \ +do { \ + if (srcsel_debug) \ + printf("%s:%d " fmt "\n",\ + __FUNCTION__, __LINE__, ##__VA_ARGS__); \ +} while (0); \ + /* * Return an IPv6 address, which is the most appropriate for a given * destination and user specified options. @@ -171,146 +198,59 @@ void addrsel_policy_init(void); * an entry to the caller for later use. */ #define REPLACE(r) do {\ - if ((r) < sizeof (ip6stat.ip6s_sources_rule) / \ - sizeof (ip6stat.ip6s_sources_rule[0])) /* check for safety */ \ - ip6stat.ip6s_sources_rule[(r)]++; \ + SASEL_LOG("REPLACE r %d ia %s ifp1 %s\n", \ + (r), s_src, ifp1->if_xname); \ + srcrule = (r); \ goto replace; \ } while (0) + #define NEXTSRC(r) do {\ - if ((r) < sizeof (ip6stat.ip6s_sources_rule) / \ - sizeof (ip6stat.ip6s_sources_rule[0])) /* check for safety */ \ - ip6stat.ip6s_sources_rule[(r)]++; \ + SASEL_LOG("NEXTSRC r %d ia %s ifp1 %s\n", \ + (r), s_src, ifp1->if_xname); \ goto next; /* XXX: we can't use 'continue' here */ \ } while (0) + #define BREAK(r) do { \ - if ((r) < sizeof (ip6stat.ip6s_sources_rule) / \ - sizeof (ip6stat.ip6s_sources_rule[0])) /* check for safety */ \ - ip6stat.ip6s_sources_rule[(r)]++; \ + SASEL_LOG("BREAK r %d ia %s ifp1 %s\n", \ + (r), s_src, ifp1->if_xname); \ + srcrule = (r); \ goto out; /* XXX: we can't use 'break' here */ \ } while (0) -/* - * 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) +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 ifnet *ifp = NULL; struct in6_ifaddr *ia = NULL, *ia_best = NULL; - struct in6_pktinfo *pi = 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; - struct in6_addrpolicy *dst_policy = NULL, *best_policy = NULL; - u_int32_t odstzone; - int prefer_tempaddr; - struct ip6_moptions *mopts; - struct ip6_out_args ip6oa = { ifscope, { 0 }, IP6OAF_SELECT_SRCIF, 0 }; - boolean_t islocal = FALSE; uint64_t secs = net_uptime(); + VERIFY(dstsock != NULL); + VERIFY(src_storage != NULL); + VERIFY(ifp != NULL); - dst = dstsock->sin6_addr; /* make a copy for local operation */ - *errorp = 0; - if (ifpp != NULL) - *ifpp = NULL; - - if (inp != NULL) { - mopts = inp->in6p_moptions; - if (INP_NO_CELLULAR(inp)) - ip6oa.ip6oa_flags |= IP6OAF_NO_CELLULAR; - if (INP_NO_EXPENSIVE(inp)) - ip6oa.ip6oa_flags |= IP6OAF_NO_EXPENSIVE; - if (INP_AWDL_UNRESTRICTED(inp)) - ip6oa.ip6oa_flags |= IP6OAF_AWDL_UNRESTRICTED; - - } else { - mopts = NULL; - } - - if (ip6oa.ip6oa_boundif != IFSCOPE_NONE) - ip6oa.ip6oa_flags |= IP6OAF_BOUND_IF; + if (sifp != NULL) + *sifp = NULL; - /* - * If the source address is explicitly specified by the caller, - * check if the requested source address is indeed a unicast address - * assigned to the node, and can be used as the packet's source - * address. If everything is okay, use the address as source. - */ - if (opts && (pi = opts->ip6po_pktinfo) && - !IN6_IS_ADDR_UNSPECIFIED(&pi->ipi6_addr)) { - struct sockaddr_in6 srcsock; - struct in6_ifaddr *ia6; + if (ifapp != NULL) + *ifapp = NULL; - /* get the outgoing interface */ - if ((*errorp = in6_selectif(dstsock, opts, mopts, ro, &ip6oa, - &ifp)) != 0) { - src_storage = NULL; - goto done; - } - - /* - * determine the appropriate zone id of the source based on - * the zone of the destination and the outgoing interface. - * If the specified address is ambiguous wrt the scope zone, - * the interface must be specified; otherwise, ifa_ifwithaddr() - * will fail matching the address. - */ - bzero(&srcsock, sizeof (srcsock)); - srcsock.sin6_family = AF_INET6; - srcsock.sin6_len = sizeof (srcsock); - srcsock.sin6_addr = pi->ipi6_addr; - if (ifp != NULL) { - *errorp = in6_setscope(&srcsock.sin6_addr, ifp, NULL); - if (*errorp != 0) { - src_storage = NULL; - goto done; - } - } - ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *) - (&srcsock)); - if (ia6 == NULL) { - *errorp = EADDRNOTAVAIL; - src_storage = NULL; - goto done; - } - IFA_LOCK_SPIN(&ia6->ia_ifa); - if ((ia6->ia6_flags & (IN6_IFF_ANYCAST | IN6_IFF_NOTREADY)) || - (inp && inp_restricted_send(inp, ia6->ia_ifa.ifa_ifp))) { - IFA_UNLOCK(&ia6->ia_ifa); - IFA_REMREF(&ia6->ia_ifa); - *errorp = EHOSTUNREACH; - src_storage = NULL; - goto done; - } + dst = dstsock->sin6_addr; /* make a copy for local operation */ - *src_storage = satosin6(&ia6->ia_addr)->sin6_addr; - IFA_UNLOCK(&ia6->ia_ifa); - IFA_REMREF(&ia6->ia_ifa); - goto done; - } + if (srcsel_debug) { + (void) inet_ntop(AF_INET6, &dst, s_dst, sizeof (s_src)); - /* - * Otherwise, if the socket has already bound the source, just use it. - */ - if (inp != NULL && !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { - src_storage = &inp->in6p_laddr; - goto done; - } - - /* - * If the address is not specified, choose the best one based on - * the outgoing interface and the destination address. - */ - - /* get the outgoing interface */ - if ((*errorp = in6_selectif(dstsock, opts, mopts, ro, &ip6oa, - &ifp)) != 0) { - src_storage = NULL; - goto done; + 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); @@ -318,44 +258,72 @@ in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, src_storage = NULL; goto done; } - lck_rw_lock_shared(&in6_ifaddr_rwlock); + 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, osrczone, dstzone; + 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 + * 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) + 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) + 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))) + (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)) + } + 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) + (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 */ @@ -406,53 +374,17 @@ in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, */ /* Rule 5: Prefer outgoing interface */ - 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 5.5: Prefer addresses in a prefix advertised by the next hop. */ - if (ro != NULL && ro->ro_rt != NULL && ia_best->ia6_ndpr != NULL && - ia->ia6_ndpr != NULL) { - struct rtentry *rta, *rtb; - int op; - - NDPR_LOCK(ia_best->ia6_ndpr); - rta = ia_best->ia6_ndpr->ndpr_rt; - if (rta != NULL) - RT_ADDREF(rta); - NDPR_UNLOCK(ia_best->ia6_ndpr); - - NDPR_LOCK(ia->ia6_ndpr); - rtb = ia->ia6_ndpr->ndpr_rt; - if (rtb != NULL) - RT_ADDREF(rtb); - NDPR_UNLOCK(ia->ia6_ndpr); - - if (rta == NULL || rtb == NULL) - op = 0; - else if (rta == ro->ro_rt && rtb != ro->ro_rt) - op = 1; - else if (rta != ro->ro_rt && rtb == ro->ro_rt) - op = 2; - else - op = 0; - - if (rta != NULL) - RT_REMREF(rta); - if (rtb != NULL) - RT_REMREF(rtb); - - switch (op) { - case 1: - NEXTSRC(IP6S_SRCRULE_5_5); - break; - case 2: - REPLACE(IP6S_SRCRULE_5_5); - break; - default: - break; - } + /* + * 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); } /* @@ -476,31 +408,17 @@ in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, * We allow users to reverse the logic by configuring * a sysctl variable, so that transparency conscious users can * always prefer stable addresses. - * Don't use temporary addresses for local destinations or - * for multicast addresses unless we were passed in an option. */ - if (IN6_IS_ADDR_MULTICAST(&dst) || - in6_matchlen(&ia_best->ia_addr.sin6_addr, &dst) >= - ia_best->ia_plen) - islocal = TRUE; - if (opts == NULL || - opts->ip6po_prefer_tempaddr == IP6PO_TEMPADDR_SYSTEM) { - prefer_tempaddr = islocal ? 0 : ip6_prefer_tempaddr; - } else if (opts->ip6po_prefer_tempaddr == - IP6PO_TEMPADDR_NOTPREFER) { - prefer_tempaddr = 0; - } else - prefer_tempaddr = 1; if (!(ia_best->ia6_flags & IN6_IFF_TEMPORARY) && (ia->ia6_flags & IN6_IFF_TEMPORARY)) { - if (prefer_tempaddr) + 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 (prefer_tempaddr) + if (hint_mask & IPV6_SRCSEL_HINT_PREFER_TMPADDR) NEXTSRC(IP6S_SRCRULE_7); else REPLACE(IP6S_SRCRULE_7); @@ -530,17 +448,40 @@ in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, * 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)); - IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for ia_best */ + 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); @@ -552,7 +493,7 @@ next: continue; out: - IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for ia_best */ + IFA_ADDREF_LOCKED(&ia->ia_ifa); /* for ia_best */ IFA_UNLOCK(&ia->ia_ifa); if (ia_best != NULL) IFA_REMREF(&ia_best->ia_ifa); @@ -562,13 +503,6 @@ out: lck_rw_done(&in6_ifaddr_rwlock); - if (ia_best != NULL && inp && - inp_restricted_send(inp, ia_best->ia_ifa.ifa_ifp)) { - IFA_REMREF(&ia_best->ia_ifa); - ia_best = NULL; - *errorp = EHOSTUNREACH; - } - if ((ia = ia_best) == NULL) { if (*errorp == 0) *errorp = EADDRNOTAVAIL; @@ -576,10 +510,190 @@ out: 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); - IFA_REMREF(&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) +{ + struct ifnet *ifp = NULL; + struct in6_pktinfo *pi = NULL; + struct ip6_moptions *mopts; + struct ip6_out_args ip6oa = { ifscope, { 0 }, IP6OAF_SELECT_SRCIF, 0, + SO_TC_UNSPEC, _NET_SERVICE_TYPE_UNSPEC }; + boolean_t inp_debug = FALSE; + uint32_t hint_mask = 0; + int prefer_tempaddr = 0; + struct ifnet *sifp = NULL; + + *errorp = 0; + if (ifpp != NULL) + *ifpp = NULL; + + if (inp != NULL) { + inp_debug = SASEL_DO_DBG(inp); + mopts = inp->in6p_moptions; + if (INP_NO_CELLULAR(inp)) + ip6oa.ip6oa_flags |= IP6OAF_NO_CELLULAR; + if (INP_NO_EXPENSIVE(inp)) + ip6oa.ip6oa_flags |= IP6OAF_NO_EXPENSIVE; + if (INP_AWDL_UNRESTRICTED(inp)) + ip6oa.ip6oa_flags |= IP6OAF_AWDL_UNRESTRICTED; + if (INP_INTCOPROC_ALLOWED(inp)) + ip6oa.ip6oa_flags |= IP6OAF_INTCOPROC_ALLOWED; + } else { + mopts = NULL; + /* Allow the kernel to retransmit packets. */ + ip6oa.ip6oa_flags |= IP6OAF_INTCOPROC_ALLOWED | + IP6OAF_AWDL_UNRESTRICTED; + } + + if (ip6oa.ip6oa_boundif != IFSCOPE_NONE) + ip6oa.ip6oa_flags |= IP6OAF_BOUND_IF; + + /* + * If the source address is explicitly specified by the caller, + * check if the requested source address is indeed a unicast address + * assigned to the node, and can be used as the packet's source + * address. If everything is okay, use the address as source. + */ + if (opts && (pi = opts->ip6po_pktinfo) && + !IN6_IS_ADDR_UNSPECIFIED(&pi->ipi6_addr)) { + struct sockaddr_in6 srcsock; + struct in6_ifaddr *ia6; + + /* get the outgoing interface */ + if ((*errorp = in6_selectif(dstsock, opts, mopts, ro, &ip6oa, + &ifp)) != 0) { + src_storage = NULL; + goto done; + } + + /* + * determine the appropriate zone id of the source based on + * the zone of the destination and the outgoing interface. + * If the specified address is ambiguous wrt the scope zone, + * the interface must be specified; otherwise, ifa_ifwithaddr() + * will fail matching the address. + */ + bzero(&srcsock, sizeof (srcsock)); + srcsock.sin6_family = AF_INET6; + srcsock.sin6_len = sizeof (srcsock); + srcsock.sin6_addr = pi->ipi6_addr; + if (ifp != NULL) { + *errorp = in6_setscope(&srcsock.sin6_addr, ifp, NULL); + if (*errorp != 0) { + src_storage = NULL; + goto done; + } + } + ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *) + (&srcsock)); + if (ia6 == NULL) { + *errorp = EADDRNOTAVAIL; + src_storage = NULL; + goto done; + } + IFA_LOCK_SPIN(&ia6->ia_ifa); + if ((ia6->ia6_flags & (IN6_IFF_ANYCAST | IN6_IFF_NOTREADY)) || + (inp && inp_restricted_send(inp, ia6->ia_ifa.ifa_ifp))) { + IFA_UNLOCK(&ia6->ia_ifa); + IFA_REMREF(&ia6->ia_ifa); + *errorp = EHOSTUNREACH; + src_storage = NULL; + goto done; + } + + *src_storage = satosin6(&ia6->ia_addr)->sin6_addr; + IFA_UNLOCK(&ia6->ia_ifa); + IFA_REMREF(&ia6->ia_ifa); + goto done; + } + + /* + * Otherwise, if the socket has already bound the source, just use it. + */ + if (inp != NULL && !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { + src_storage = &inp->in6p_laddr; + goto done; + } + + /* + * If the address is not specified, choose the best one based on + * the outgoing interface and the destination address. + */ + /* get the outgoing interface */ + if ((*errorp = in6_selectif(dstsock, opts, mopts, ro, &ip6oa, + &ifp)) != 0) { + src_storage = NULL; + goto done; + } + + VERIFY(ifp != NULL); + + if (opts == NULL || + opts->ip6po_prefer_tempaddr == IP6PO_TEMPADDR_SYSTEM) { + prefer_tempaddr = ip6_prefer_tempaddr; + } else if (opts->ip6po_prefer_tempaddr == IP6PO_TEMPADDR_NOTPREFER) { + prefer_tempaddr = 0; + } else + prefer_tempaddr = 1; + + if (prefer_tempaddr) + hint_mask |= IPV6_SRCSEL_HINT_PREFER_TMPADDR; + + if (in6_selectsrc_core(dstsock, hint_mask, ifp, inp_debug, src_storage, + &sifp, errorp, NULL) == NULL) { + src_storage = NULL; + goto done; + } + + VERIFY(sifp != NULL); + + if (inp && inp_restricted_send(inp, sifp)) { + src_storage = NULL; + *errorp = EHOSTUNREACH; + ifnet_release(sifp); + goto done; + } else { + ifnet_release(sifp); + } + done: if (ifpp != NULL) { /* if ifp is non-NULL, refcnt held in in6_selectif() */ @@ -625,21 +739,6 @@ selectroute(struct sockaddr_in6 *srcsock, struct sockaddr_in6 *dstsock, unsigned int ifscope = ((ip6oa != NULL) ? ip6oa->ip6oa_boundif : IFSCOPE_NONE); -#if 0 - char ip6buf[INET6_ADDRSTRLEN]; - - if (dstsock->sin6_addr.s6_addr32[0] == 0 && - dstsock->sin6_addr.s6_addr32[1] == 0 && - !IN6_IS_ADDR_LOOPBACK(&dstsock->sin6_addr)) { - printf("in6_selectroute: strange destination %s\n", - ip6_sprintf(ip6buf, &dstsock->sin6_addr)); - } else { - printf("in6_selectroute: destination = %s%%%d\n", - ip6_sprintf(ip6buf, &dstsock->sin6_addr), - dstsock->sin6_scope_id); /* for debug */ - } -#endif - if (retifp != NULL) *retifp = NULL; @@ -665,16 +764,13 @@ selectroute(struct sockaddr_in6 *srcsock, struct sockaddr_in6 *dstsock, * Perform source interface selection only if Scoped Routing * is enabled and a source address that isn't unspecified. */ - select_srcif = (ip6_doscopedroute && srcsock != NULL && + select_srcif = (srcsock != NULL && !IN6_IS_ADDR_UNSPECIFIED(&srcsock->sin6_addr)); - /* - * If Scoped Routing is disabled, ignore the given ifscope. - * Otherwise even if source selection won't be performed, - * we still obey IPV6_BOUND_IF. - */ - if (!ip6_doscopedroute && ifscope != IFSCOPE_NONE) - ifscope = IFSCOPE_NONE; + if (ip6_select_srcif_debug) { + printf("%s src %s dst %s ifscope %d select_srcif %d\n", + __func__, s_src, s_dst, ifscope, select_srcif); + } /* If the caller specified the outgoing interface explicitly, use it */ if (opts != NULL && (pi = opts->ip6po_pktinfo) != NULL && @@ -721,7 +817,7 @@ getsrcif: * If the outgoing interface was not set via IPV6_BOUND_IF or * IPV6_PKTINFO, use the scope ID in the destination address. */ - if (ip6_doscopedroute && ifscope == IFSCOPE_NONE) + if (ifscope == IFSCOPE_NONE) ifscope = dstsock->sin6_scope_id; /* @@ -792,12 +888,15 @@ getsrcif: if (ip6_select_srcif_debug && ifa != NULL) { if (ro->ro_rt != NULL) { - printf("%s->%s ifscope %d->%d ifa_if %s " - "ro_if %s\n", s_src, s_dst, ifscope, + printf("%s %s->%s ifscope %d->%d ifa_if %s " + "ro_if %s\n", + __func__, + s_src, s_dst, ifscope, scope, if_name(ifa->ifa_ifp), if_name(rt_ifp)); } else { - printf("%s->%s ifscope %d->%d ifa_if %s\n", + printf("%s %s->%s ifscope %d->%d ifa_if %s\n", + __func__, s_src, s_dst, ifscope, scope, if_name(ifa->ifa_ifp)); } @@ -827,10 +926,14 @@ getsrcif: ifa = (struct ifaddr *)ifa_foraddr6(&srcsock->sin6_addr); if (ip6_select_srcif_debug && ifa != NULL) { - printf("%s->%s ifscope %d ifa_if %s\n", + printf("%s %s->%s ifscope %d ifa_if %s\n", + __func__, s_src, s_dst, ifscope, if_name(ifa->ifa_ifp)); + } else if (ip6_select_srcif_debug) { + printf("%s %s->%s ifscope %d ifa_if NULL\n", + __func__, + s_src, s_dst, ifscope); } - } getroute: @@ -1063,12 +1166,14 @@ done: IFNET_IS_CELLULAR(_ifp)) || \ (((_ip6oa)->ip6oa_flags & IP6OAF_NO_EXPENSIVE) && \ IFNET_IS_EXPENSIVE(_ifp)) || \ + (!((_ip6oa)->ip6oa_flags & IP6OAF_INTCOPROC_ALLOWED) && \ + IFNET_IS_INTCOPROC(_ifp)) || \ (!((_ip6oa)->ip6oa_flags & IP6OAF_AWDL_UNRESTRICTED) && \ - IFNET_IS_AWDL_RESTRICTED(_ifp))) + IFNET_IS_AWDL_RESTRICTED(_ifp))) if (error == 0 && ip6oa != NULL && ((ifp && CHECK_RESTRICTIONS(ip6oa, ifp)) || - (route && route->ro_rt && + (route && route->ro_rt && CHECK_RESTRICTIONS(ip6oa, route->ro_rt->rt_ifp)))) { if (route != NULL && route->ro_rt != NULL) { ROUTE_RELEASE(route); @@ -1119,8 +1224,10 @@ done: if (error == 0) { if (retrt != NULL && route != NULL) *retrt = route->ro_rt; /* ro_rt may be NULL */ - } else if (select_srcif && ip6_select_srcif_debug) { - printf("%s->%s ifscope %d ifa_if %s ro_if %s (error=%d)\n", + } + if (ip6_select_srcif_debug) { + printf("%s %s->%s ifscope %d ifa_if %s ro_if %s (error=%d)\n", + __func__, s_src, s_dst, ifscope, (ifa != NULL) ? if_name(ifa->ifa_ifp) : "NONE", (ifp != NULL) ? if_name(ifp) : "NONE", error); @@ -1137,7 +1244,7 @@ done: * caller provides a non-NULL retifp. The caller is responsible for checking * if the returned ifp is valid and release its reference at all times. */ -static int +int in6_selectif(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, struct ip6_moptions *mopts, struct route_in6 *ro, struct ip6_out_args *ip6oa, struct ifnet **retifp) @@ -1235,25 +1342,19 @@ in6_selecthlim(struct in6pcb *in6p, struct ifnet *ifp) { if (in6p && in6p->in6p_hops >= 0) { return (in6p->in6p_hops); - } else { - lck_rw_lock_shared(nd_if_rwlock); - if (ifp && ifp->if_index < nd_ifinfo_indexlim) { - u_int8_t chlim; - struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index]; - - if (ndi->initialized) { - /* access chlim without lock, for performance */ - chlim = ndi->chlim; - } else { - chlim = ip6_defhlim; - } - lck_rw_done(nd_if_rwlock); - return (chlim); + } else if (NULL != ifp) { + u_int8_t chlim; + struct nd_ifinfo *ndi = ND_IFINFO(ifp); + if (ndi && ndi->initialized) { + /* access chlim without lock, for performance */ + chlim = ndi->chlim; } else { - lck_rw_done(nd_if_rwlock); - return (ip6_defhlim); + chlim = ip6_defhlim; } + return (chlim); } + + return (ip6_defhlim); } /* @@ -1268,6 +1369,7 @@ in6_pcbsetport(struct in6_addr *laddr, struct inpcb *inp, struct proc *p, struct socket *so = inp->inp_socket; u_int16_t lport = 0, first, last, *lastport; int count, error = 0, wild = 0; + bool found; struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; kauth_cred_t cred; if (!locked) { /* Make sure we don't run into a deadlock: 4052373 */ @@ -1330,6 +1432,7 @@ in6_pcbsetport(struct in6_addr *laddr, struct inpcb *inp, struct proc *p, * counting down */ count = first - last; + found = false; do { if (count-- < 0) { /* completely used? */ @@ -1347,11 +1450,14 @@ in6_pcbsetport(struct in6_addr *laddr, struct inpcb *inp, struct proc *p, if (*lastport > first || *lastport < last) *lastport = first; lport = htons(*lastport); - } while (in6_pcblookup_local(pcbinfo, &inp->in6p_laddr, lport, - wild)); + + found = in6_pcblookup_local(pcbinfo, &inp->in6p_laddr, + lport, wild) == NULL; + } while (!found); } else { /* counting up */ count = last - first; + found = false; do { if (count-- < 0) { /* completely used? */ @@ -1369,8 +1475,10 @@ in6_pcbsetport(struct in6_addr *laddr, struct inpcb *inp, struct proc *p, if (*lastport < first || *lastport > last) *lastport = first; lport = htons(*lastport); - } while (in6_pcblookup_local(pcbinfo, &inp->in6p_laddr, lport, - wild)); + + found = in6_pcblookup_local(pcbinfo, &inp->in6p_laddr, + lport, wild) == NULL; + } while (!found); } inp->inp_lport = lport;