git: b9772822a6b3 - stable/13 - routing: fix source address selection rules for IPv4 over IPv6.

From: Alexander V. Chernikov <melifaro_at_FreeBSD.org>
Date: Sat, 04 Dec 2021 19:15:35 UTC
The branch stable/13 has been updated by melifaro:

URL: https://cgit.FreeBSD.org/src/commit/?id=b9772822a6b363d2dad42ae8915730405986edb9

commit b9772822a6b363d2dad42ae8915730405986edb9
Author:     Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2021-09-06 22:08:15 +0000
Commit:     Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2021-12-04 19:02:52 +0000

    routing: fix source address selection rules for IPv4 over IPv6.
    
    Current logic always selects an IFA of the same family from the
     outgoing interfaces. In IPv4 over IPv6 setup there can be just
     single non-127.0.0.1 ifa, attached to the loopback interface.
    
    Create a separate rt_getifa_family() to handle entire ifa selection
     for the IPv4 over IPv6.
    
    Differential Revision: https://reviews.freebsd.org/D31868
    MFC after:      1 week
    
    (cherry picked from commit 4b631fc832acf1bab24aa88aa06229d368d8e131)
---
 sys/net/route.c  | 38 ++++++++++++++++++++++++++++++++++++++
 sys/netinet/in.c | 34 ++++++++++++++++++++++++++++++++++
 sys/netinet/in.h |  1 +
 3 files changed, 73 insertions(+)

diff --git a/sys/net/route.c b/sys/net/route.c
index a24438563f50..b2c9051d98c0 100644
--- a/sys/net/route.c
+++ b/sys/net/route.c
@@ -532,6 +532,41 @@ info_get_ifp(struct rt_addrinfo *info)
 	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.
  *
@@ -564,6 +599,9 @@ rt_getifa_fib(struct rt_addrinfo *info, u_int fibnum)
 	 */
 	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.c b/sys/netinet/in.c
index a0ea17e47154..b51f1111b88a 100644
--- a/sys/netinet/in.c
+++ b/sys/netinet/in.c
@@ -194,6 +194,40 @@ in_localip_more(struct in_ifaddr *original_ia)
 	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
diff --git a/sys/netinet/in.h b/sys/netinet/in.h
index 28d6721edc53..0206fd16d2fe 100644
--- a/sys/netinet/in.h
+++ b/sys/netinet/in.h
@@ -651,6 +651,7 @@ int	 in_canforward(struct in_addr);
 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 */