diff --git a/sys/net/route.c b/sys/net/route.c --- a/sys/net/route.c +++ b/sys/net/route.c @@ -534,6 +534,41 @@ return (NULL); } +/* + * Calculates proper ifa/ifp for the cases when gateway AF is different + * from dst AF. + * + * Returns 0 on success. + */ +__noinline static int +rt_getifa_family(struct rt_addrinfo *info, uint32_t fibnum) +{ + if (info->rti_ifp == NULL) { + struct ifaddr *ifa = NULL; + /* + * No transmit interface specified. Guess it by checking gw sa. + */ + const struct sockaddr *gw = info->rti_info[RTAX_GATEWAY]; + ifa = ifa_ifwithroute(RTF_GATEWAY, gw, gw, fibnum); + if (ifa == NULL) + return (ENETUNREACH); + info->rti_ifp = ifa->ifa_ifp; + } + + /* Prefer address from outgoing interface */ + info->rti_ifa = ifaof_ifpforaddr(info->rti_info[RTAX_DST], info->rti_ifp); +#ifdef INET + if (info->rti_ifa == NULL) { + /* Use first found IPv4 address */ + bool loopback_ok = info->rti_ifp->if_flags & IFF_LOOPBACK; + info->rti_ifa = (struct ifaddr *)in_findlocal(fibnum, loopback_ok); + } +#endif + if (info->rti_ifa == NULL) + return (ENETUNREACH); + return (0); +} + /* * Look up rt_addrinfo for a specific fib. * @@ -566,6 +601,9 @@ */ if (info->rti_ifa == NULL && ifaaddr != NULL) info->rti_ifa = ifa_ifwithaddr(ifaaddr); + if ((info->rti_ifa == NULL) && ((info->rti_flags & RTF_GATEWAY) != 0) && + (gateway->sa_family != dst->sa_family)) + return (rt_getifa_family(info, fibnum)); if (info->rti_ifa == NULL) { const struct sockaddr *sa; diff --git a/sys/netinet/in.h b/sys/netinet/in.h --- a/sys/netinet/in.h +++ b/sys/netinet/in.h @@ -651,6 +651,7 @@ int in_localaddr(struct in_addr); int in_localip(struct in_addr); int in_ifhasaddr(struct ifnet *, struct in_addr); +struct in_ifaddr *in_findlocal(uint32_t, bool); int inet_aton(const char *, struct in_addr *); /* in libkern */ char *inet_ntoa_r(struct in_addr ina, char *buf); /* in libkern */ char *inet_ntop(int, const void *, char *, socklen_t); /* in libkern */ diff --git a/sys/netinet/in.c b/sys/netinet/in.c --- a/sys/netinet/in.c +++ b/sys/netinet/in.c @@ -188,6 +188,40 @@ return (NULL); } +/* + * Tries to find first IPv4 address in the provided fib. + * Prefers non-loopback addresses and return loopback IFF + * @loopback_ok is set. + * + * Returns ifa or NULL. + */ +struct in_ifaddr * +in_findlocal(uint32_t fibnum, bool loopback_ok) +{ + struct rm_priotracker in_ifa_tracker; + struct in_ifaddr *ia = NULL, *ia_lo = NULL; + + NET_EPOCH_ASSERT(); + + IN_IFADDR_RLOCK(&in_ifa_tracker); + CK_STAILQ_FOREACH(ia, &V_in_ifaddrhead, ia_link) { + uint32_t ia_fib = ia->ia_ifa.ifa_ifp->if_fib; + if (!V_rt_add_addr_allfibs && (fibnum != ia_fib)) + continue; + + if (!IN_LOOPBACK(ntohl(IA_SIN(ia)->sin_addr.s_addr))) + break; + if (loopback_ok) + ia_lo = ia; + } + IN_IFADDR_RUNLOCK(&in_ifa_tracker); + + if (ia == NULL) + ia = ia_lo; + + return (ia); +} + /* * Determine whether an IP address is in a reserved set of addresses * that may not be forwarded, or whether datagrams to that destination