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 in6_addr dst; struct ifnet *ifp = NULL; struct in6_ifaddr *ia = NULL, *ia_best = NULL; struct in6_pktinfo *pi = 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 timeval timenow; unsigned int nocell; boolean_t islocal = FALSE; getmicrotime(&timenow); dst = dstsock->sin6_addr; /* make a copy for local operation */ *errorp = 0; if (ifpp != NULL) *ifpp = NULL; if (inp != NULL) { mopts = inp->in6p_moptions; nocell = (inp->inp_flags & INP_NO_IFT_CELLULAR) ? 1 : 0; } else { mopts = NULL; nocell = 0; } /* * 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, ifscope, nocell, &ifp)) != 0) { return (NULL); } /* * 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) { *errorp = in6_setscope(&srcsock.sin6_addr, ifp, NULL); if (*errorp != 0) { ifnet_release(ifp); return (NULL); } } ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *)(&srcsock)); if (ia6 == NULL) { *errorp = EADDRNOTAVAIL; if (ifp != NULL) ifnet_release(ifp); return (NULL); } IFA_LOCK_SPIN(&ia6->ia_ifa); if ((ia6->ia6_flags & (IN6_IFF_ANYCAST | IN6_IFF_NOTREADY)) || (nocell && (ia6->ia_ifa.ifa_ifp->if_type == IFT_CELLULAR))) { IFA_UNLOCK(&ia6->ia_ifa); IFA_REMREF(&ia6->ia_ifa); *errorp = EADDRNOTAVAIL; if (ifp != NULL) ifnet_release(ifp); return (NULL); } *src_storage = satosin6(&ia6->ia_addr)->sin6_addr; IFA_UNLOCK(&ia6->ia_ifa); IFA_REMREF(&ia6->ia_ifa); if (ifpp != NULL) { /* if ifp is non-NULL, refcnt held in in6_selectif() */ *ifpp = ifp; } else if (ifp != NULL) { ifnet_release(ifp); } return (src_storage); } /* * Otherwise, if the socket has already bound the source, just use it. */ if (inp != NULL && !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) return (&inp->in6p_laddr); /* * 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, ifscope, nocell, &ifp)) != 0) return (NULL); #ifdef DIAGNOSTIC if (ifp == NULL) /* this should not happen */ panic("in6_selectsrc: NULL ifp"); #endif *errorp = in6_setscope(&dst, ifp, &odstzone); if (*errorp != 0) { if (ifp != NULL) ifnet_release(ifp); return (NULL); } 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; struct in6_addr src; struct ifnet *ifp1 = ia->ia_ifp; IFA_LOCK(&ia->ia_ifa); /* * 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) goto next; src = ia->ia_addr.sin6_addr; if (in6_setscope(&src, ifp, &osrczone) || in6_setscope(&src, ifp1, &srczone) || osrczone != srczone) goto next; /* avoid unusable addresses */ if ((ia->ia6_flags & (IN6_IFF_NOTREADY | IN6_IFF_ANYCAST | IN6_IFF_DETACHED))) goto next; if (!ip6_use_deprecated && IFA6_IS_DEPRECATED(ia)) goto next; /* Rule 1: Prefer same address */ if (IN6_ARE_ADDR_EQUAL(&dst, &ia->ia_addr.sin6_addr)) BREAK(1); /* there should be no better candidate */ if (ia_best == NULL) REPLACE(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(2); NEXTSRC(2); } else if (IN6_ARE_SCOPE_CMP(new_scope, best_scope) < 0) { if (IN6_ARE_SCOPE_CMP(new_scope, dst_scope) < 0) NEXTSRC(2); REPLACE(2); } /* * Rule 3: Avoid deprecated addresses. Note that the case of * !ip6_use_deprecated is already rejected above. */ if (!IFA6_IS_DEPRECATED(ia_best) && IFA6_IS_DEPRECATED(ia)) NEXTSRC(3); if (IFA6_IS_DEPRECATED(ia_best) && !IFA6_IS_DEPRECATED(ia)) REPLACE(3); /* Rule 4: Prefer home addresses */ /* * XXX: This is a TODO. We should probably merge the MIP6 * case above. */ /* Rule 5: Prefer outgoing interface */ if (ia_best->ia_ifp == ifp && ia->ia_ifp != ifp) NEXTSRC(5); if (ia_best->ia_ifp != ifp && ia->ia_ifp == ifp) REPLACE(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(6); if (dst_policy->label != best_policy->label && dst_policy->label == new_policy->label) REPLACE(6); } /* * Rule 7: Prefer public addresses. * We allow users to reverse the logic by configuring * a sysctl variable, so that privacy conscious users can * always prefer temporary 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) >= in6_mask2len(&ia_best->ia_prefixmask.sin6_addr, NULL)) 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) REPLACE(7); else NEXTSRC(7); } if ((ia_best->ia6_flags & IN6_IFF_TEMPORARY) && !(ia->ia6_flags & IN6_IFF_TEMPORARY)) { if (prefer_tempaddr) NEXTSRC(7); else REPLACE(7); } /* * Rule 8: 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(8); if (!(ia_best->ia_ifp->if_flags & IFF_UP) && (ia->ia_ifp->if_flags & IFF_UP)) REPLACE(8); /* * Rule 14: Use longest matching prefix. * Note: in the address selection draft, this rule is * documented as "Rule 8". However, since it is also * documented that this rule can be overridden, we assign * a large number so that it is easy to assign smaller numbers * to more preferred rules. */ new_matchlen = in6_matchlen(&ia->ia_addr.sin6_addr, &dst); if (best_matchlen < new_matchlen) REPLACE(14); if (new_matchlen < best_matchlen) NEXTSRC(14); /* Rule 15 is reserved. */ /* * Last resort: just keep the current candidate. * Or, do we need more rules? */ IFA_UNLOCK(&ia->ia_ifa); continue; replace: 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 */ 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 (nocell && ia_best != NULL && (ia_best->ia_ifa.ifa_ifp->if_type == IFT_CELLULAR)) { IFA_REMREF(&ia_best->ia_ifa); ia_best = NULL; } if ( (ia = ia_best) == NULL) { *errorp = EADDRNOTAVAIL; if (ifp != NULL) ifnet_release(ifp); return (NULL); } IFA_LOCK_SPIN(&ia->ia_ifa); *src_storage = satosin6(&ia->ia_addr)->sin6_addr; IFA_UNLOCK(&ia->ia_ifa); IFA_REMREF(&ia->ia_ifa); if (ifpp != NULL) { /* if ifp is non-NULL, refcnt held in in6_selectif() */ *ifpp = ifp; } else if (ifp != NULL) { ifnet_release(ifp); } return (src_storage); }
struct in6_addr * in6_selectsrc(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts, struct ip6_moptions *mopts, struct route *ro, struct ifnet **ifpp, struct in6_addr *laddr, int *errorp) { struct in6_addr *dst; struct ifnet *ifp = NULL; struct in6_ifaddr *ia = NULL, *ia_best = NULL; struct in6_pktinfo *pi = NULL; int dst_scope = -1, best_scope = -1, best_matchlen = -1; struct in6_addrpolicy *dst_policy = NULL, *best_policy = NULL; #ifdef MIP6 struct hif_softc *sc; #ifdef MIP6_ALLOW_COA_FALLBACK struct mip6_bu *mbu_dst; u_int8_t coafallback = 0; #endif #endif dst = &dstsock->sin6_addr; *errorp = 0; if (ifpp) *ifpp = 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; /* get the outgoing interface */ if ((*errorp = in6_selectif(dstsock, opts, mopts, ro, &ifp)) != 0) { return(NULL); } /* * determine the appropriate zone id of the source based on * the zone of the destination and the outgoing interface. */ bzero(&srcsock, sizeof(srcsock)); srcsock.sin6_family = AF_INET6; srcsock.sin6_len = sizeof(srcsock); srcsock.sin6_addr = pi->ipi6_addr; if (ifp) { int64_t zone; zone = in6_addr2zoneid(ifp, &pi->ipi6_addr); if (zone < 0) { /* XXX: this should not happen */ *errorp = EINVAL; return(NULL); } srcsock.sin6_scope_id = zone; } if ((*errorp = in6_embedscope(&srcsock.sin6_addr, &srcsock)) != 0) { return(NULL); } #ifndef SCOPEDROUTING srcsock.sin6_scope_id = 0; /* XXX: ifa_ifwithaddr expects 0 */ #endif ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *)(&srcsock)); if (ia6 == NULL || (ia6->ia6_flags & (IN6_IFF_ANYCAST | IN6_IFF_NOTREADY))) { *errorp = EADDRNOTAVAIL; return(NULL); } pi->ipi6_addr = srcsock.sin6_addr; /* XXX: this overrides pi */ if (*ifpp) *ifpp = ifp; return(&pi->ipi6_addr); } /* * Otherwise, if the socket has already bound the source, just use it. */ if (laddr && !IN6_IS_ADDR_UNSPECIFIED(laddr)) return(laddr); /* * 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, &ifp)) != 0) return(NULL); #ifdef MIP6 #ifdef MIP6_ALLOW_COA_FALLBACK for (sc = TAILQ_FIRST(&hif_softc_list); sc; sc = TAILQ_NEXT(sc, hif_entry)) { mbu_dst = mip6_bu_list_find_withpaddr(&sc->hif_bu_list, dst); if (mbu_dst != NULL) coafallback = mbu_dst->mbu_coafallback; } #endif /* MIP6_ALLOW_COA_FALLBACK */ #endif /* MIP6 */ #ifdef DIAGNOSTIC if (ifp == NULL) /* this should not happen */ panic("in6_selectsrc: NULL ifp"); #endif for (ia = in6_ifaddr; ia; ia = ia->ia_next) { int new_scope = -1, new_matchlen = -1; struct in6_addrpolicy *new_policy = NULL; int64_t srczone, dstzone; struct ifnet *ifp1 = ia->ia_ifp; /* * 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 ((dstzone = in6_addr2zoneid(ifp1, dst)) < 0 || dstzone != in6_addr2zoneid(ifp, dst)) { continue; } if ((srczone = in6_addr2zoneid(ifp1, &ia->ia_addr.sin6_addr)) < 0 || srczone != in6_addr2zoneid(ifp, &ia->ia_addr.sin6_addr)) { continue; } /* avoid unusable addresses */ if ((ia->ia6_flags & (IN6_IFF_NOTREADY | IN6_IFF_ANYCAST | IN6_IFF_DETACHED))) { continue; } if (!ip6_use_deprecated && IFA6_IS_DEPRECATED(ia)) continue; /* Rule 1: Prefer same address */ if (IN6_ARE_ADDR_EQUAL(dst, &ia->ia_addr.sin6_addr)) { ia_best = ia; BREAK(1); /* there should be no better candidate */ } if (ia_best == NULL) REPLACE(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(2); NEXT(2); } else if (IN6_ARE_SCOPE_CMP(new_scope, best_scope) < 0) { if (IN6_ARE_SCOPE_CMP(new_scope, dst_scope) < 0) NEXT(2); REPLACE(2); } /* * Rule 3: Avoid deprecated addresses. Note that the case of * !ip6_use_deprecated is already rejected above. */ if (!IFA6_IS_DEPRECATED(ia_best) && IFA6_IS_DEPRECATED(ia)) NEXT(3); if (IFA6_IS_DEPRECATED(ia_best) && !IFA6_IS_DEPRECATED(ia)) REPLACE(3); /* Rule 4: Prefer home addresses */ /* * XXX: This is a TODO. We should probably merge the MIP6 * case above. */ #ifdef MIP6 /* * If SA is simultaneously a home address and care-of address * and SB is not, then prefer SA. Similarly, if SB is * simultaneously a home address and care-of address and SA is * not, then prefer SB. */ { struct mip6_bu *mbu_ia_best = NULL, *mbu_ia = NULL; if (ia_best->ia6_flags & IN6_IFF_HOME) { /* * find a binding update entry for ia_best. */ for (sc = TAILQ_FIRST(&hif_softc_list); sc; sc = TAILQ_NEXT(sc, hif_entry)) { mbu_ia_best = mip6_bu_list_find_home_registration( &sc->hif_bu_list, &ia->ia_addr.sin6_addr); if (mbu_ia_best) break; } } if (ia->ia6_flags & IN6_IFF_HOME) { /* * find a binding update entry for ia. */ for (sc = TAILQ_FIRST(&hif_softc_list); sc; sc = TAILQ_NEXT(sc, hif_entry)) { mbu_ia = mip6_bu_list_find_home_registration( &sc->hif_bu_list, &ia->ia_addr.sin6_addr); if (mbu_ia) break; } } /* * if the binding update entry for a certain address * exists and its registration status is * MIP6_BU_REG_STATE_NOTREG, the address is a home * address and a care of addres simultaneously. */ if ((mbu_ia_best && (mbu_ia_best->mbu_reg_state == MIP6_BU_REG_STATE_NOTREG)) && !(mbu_ia && (mbu_ia->mbu_reg_state == MIP6_BU_REG_STATE_NOTREG))) { NEXT(4); } if (!(mbu_ia_best && (mbu_ia_best->mbu_reg_state == MIP6_BU_REG_STATE_NOTREG)) && (mbu_ia && (mbu_ia->mbu_reg_state == MIP6_BU_REG_STATE_NOTREG))) { REPLACE(4); } } #ifdef MIP6_ALLOW_COA_FALLBACK if (coafallback) { /* * if the peer doesn't recognize a home * address destination option, we will use a * CoA as a source address instead of a home * address we have registered before. Though * this behavior may arouse a mip6 beleiver's * anger, is very useful in the current * transition period that many hosts don't * recognize a home address destination * option... */ if ((ia_best->ia6_flags & IN6_IFF_HOME) == 0 && (ia->ia6_flags & IN6_IFF_HOME) != 0) { /* XXX will break stat! */ NEXT(0); } if ((ia_best->ia6_flags & IN6_IFF_HOME) != 0 && (ia->ia6_flags & IN6_IFF_HOME) == 0) { /* XXX will break stat! */ REPLACE(0); } } else #endif { /* * If SA is just a home address and SB is just * a care-of address, then prefer * SA. Similarly, if SB is just a home address * and SA is just a care-of address, then * prefer SB. */ if ((ia_best->ia6_flags & IN6_IFF_HOME) != 0 && (ia->ia6_flags & IN6_IFF_HOME) == 0) { NEXT(4); } if ((ia_best->ia6_flags & IN6_IFF_HOME) == 0 && (ia->ia6_flags & IN6_IFF_HOME) != 0) { REPLACE(4); } } #endif /* MIP6 */ /* Rule 5: Prefer outgoing interface */ if (ia_best->ia_ifp == ifp && ia->ia_ifp != ifp) NEXT(5); if (ia_best->ia_ifp != ifp && ia->ia_ifp == ifp) REPLACE(5); /* * Rule 6: Prefer matching label * Note that best_policy should be non-NULL here. */ if (dst_policy == NULL) dst_policy = lookup_addrsel_policy(dstsock); if (dst_policy->label != ADDR_LABEL_NOTAPP) { new_policy = lookup_addrsel_policy(&ia->ia_addr); if (dst_policy->label == best_policy->label && dst_policy->label != new_policy->label) NEXT(6); if (dst_policy->label != best_policy->label && dst_policy->label == new_policy->label) REPLACE(6); } /* * Rule 7: Prefer public addresses. * We allow users to reverse the logic by configuring * a sysctl variable, so that privacy conscious users can * always prefer temporary addresses. */ if (!(ia_best->ia6_flags & IN6_IFF_TEMPORARY) && (ia->ia6_flags & IN6_IFF_TEMPORARY)) { if (ip6_prefer_tempaddr) REPLACE(7); else NEXT(7); } if ((ia_best->ia6_flags & IN6_IFF_TEMPORARY) && !(ia->ia6_flags & IN6_IFF_TEMPORARY)) { if (ip6_prefer_tempaddr) NEXT(7); else REPLACE(7); } /* * Rule 8: 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)) NEXT(8); if (!(ia_best->ia_ifp->if_flags & IFF_UP) && (ia->ia_ifp->if_flags & IFF_UP)) REPLACE(8); /* * Rule 9: prefer addresses on "preferred" interfaces. * This is a KAME specific rule. */ #define NDI_BEST (nd_ifinfo[ia_best->ia_ifp->if_index]) #define NDI_NEW (nd_ifinfo[ia->ia_ifp->if_index]) if ((NDI_BEST.flags & ND6_IFF_PREFER_SOURCE) && !(NDI_NEW.flags & ND6_IFF_PREFER_SOURCE)) NEXT(9); if (!(NDI_BEST.flags & ND6_IFF_PREFER_SOURCE) && (NDI_NEW.flags & ND6_IFF_PREFER_SOURCE)) REPLACE(9); #undef NDI_BEST #undef NDI_NEW /* * Rule 14: Use longest matching prefix. * Note: in the address selection draft, this rule is * documented as "Rule 8". However, since it is also * documented that this rule can be overridden, we assign * a large number so that it is easy to assign smaller numbers * to more preferred rules. */ new_matchlen = in6_matchlen(&ia->ia_addr.sin6_addr, dst); if (best_matchlen < new_matchlen) REPLACE(14); if (new_matchlen < best_matchlen) NEXT(14); /* Rule 15 is reserved. */ /* * Last resort: just keep the current candidate. * Or, do we need more rules? */ continue; replace: ia_best = ia; best_scope = (new_scope >= 0 ? new_scope : in6_addrscope(&ia_best->ia_addr.sin6_addr)); best_policy = (new_policy ? new_policy : lookup_addrsel_policy(&ia_best->ia_addr)); best_matchlen = (new_matchlen >= 0 ? new_matchlen : in6_matchlen(&ia_best->ia_addr.sin6_addr, dst)); next: continue; out: break; } if ((ia = ia_best) == NULL) { *errorp = EADDRNOTAVAIL; return(NULL); } if (ifpp) *ifpp = ifp; return(&ia->ia_addr.sin6_addr); }