git: 84f790e5b6cc - main - ip_encap: Recompute flowid for packets received over tunnel

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Tue, 13 Oct 2026 21:01:26 UTC
The branch main has been updated by pouria:

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

commit 84f790e5b6ccca9d7253834b7dcac5f4432b2917
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-10-09 11:08:44 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-10-13 20:59:03 +0000

    ip_encap: Recompute flowid for packets received over tunnel
    
    Packets decapsulated by a tunnel interface carry the NIC's flowid
    computed over the outer tunnel tuple, which is the same for every
    flow a peer carries. Therefore mpath nhop selection places all
    traffic on one path.
    
    Add encap_set_flowid() to ip_encap.c, which rehashes a decapsulated
    IP packet on its inner addresses.
    
    The feature is off by default and is enabled with the per-vnet
    net.route.hash_inbound sysctl.
    
    Reviewed by:    gallatin
    Differential Revision:  https://reviews.freebsd.org/D60552
---
 sys/dev/wg/if_wg.c     |  3 +++
 sys/net/if_geneve.c    |  3 +++
 sys/net/if_gif.c       |  2 ++
 sys/net/if_gre.c       |  2 ++
 sys/net/if_ovpn.c      |  3 +++
 sys/netinet/ip_encap.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++
 sys/netinet/ip_encap.h |  7 +++++++
 7 files changed, 72 insertions(+)

diff --git a/sys/dev/wg/if_wg.c b/sys/dev/wg/if_wg.c
index ffae47938e0a..5877c1966512 100644
--- a/sys/dev/wg/if_wg.c
+++ b/sys/dev/wg/if_wg.c
@@ -43,6 +43,7 @@
 #include <netinet6/in6_var.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
+#include <netinet/ip_encap.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
 #include <netinet/udp_var.h>
@@ -1833,6 +1834,8 @@ wg_deliver_in(struct wg_peer *peer)
 
 		CURVNET_SET(if_getvnet(ifp));
 		M_SETFIB(m, if_getfib(ifp));
+		if (V_encap_hash_inbound)
+			encap_set_flowid(m, af);
 #ifdef DEV_NETMAP
 		if ((if_getcapenable(ifp) & IFCAP_NETMAP) != 0)
 			wg_deliver_netmap(ifp, m, af);
diff --git a/sys/net/if_geneve.c b/sys/net/if_geneve.c
index a8947bf4bd52..6413506518ef 100644
--- a/sys/net/if_geneve.c
+++ b/sys/net/if_geneve.c
@@ -68,6 +68,7 @@
 #include <netinet/in_var.h>
 #include <netinet/in_pcb.h>
 #include <netinet/ip.h>
+#include <netinet/ip_encap.h>
 #include <netinet/ip_var.h>
 #include <netinet/ip6.h>
 #include <netinet6/ip6_var.h>
@@ -3063,6 +3064,8 @@ geneve_udp_input(struct mbuf *m, int offset, struct inpcb *inpcb,
 	if (proto == GENEVE_PROTO_ETHER)
 		(*ifp->if_input)(ifp, m);
 	else {
+		if (V_encap_hash_inbound)
+			encap_set_flowid(m, af);
 		BPF_MTAP2(ifp, &af, sizeof(af), m);
 		netisr_dispatch_src(info.isr, (uintptr_t)xgnvso, m);
 	}
diff --git a/sys/net/if_gif.c b/sys/net/if_gif.c
index 3b5a641af0c6..c12c0de98864 100644
--- a/sys/net/if_gif.c
+++ b/sys/net/if_gif.c
@@ -727,6 +727,8 @@ gif_input(struct mbuf *m, struct ifnet *ifp, int proto, uint8_t ecn)
 	if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
 	if_inc_counter(ifp, IFCOUNTER_IBYTES, m->m_pkthdr.len);
 	M_SETFIB(m, ifp->if_fib);
+	if (V_encap_hash_inbound)
+		encap_set_flowid(m, af);
 	netisr_dispatch(isr, m);
 	return;
 drop:
diff --git a/sys/net/if_gre.c b/sys/net/if_gre.c
index c390e552886a..f29a98c1f365 100644
--- a/sys/net/if_gre.c
+++ b/sys/net/if_gre.c
@@ -825,6 +825,8 @@ gre_input(struct mbuf *m, int off, int proto, void *arg)
 	m_clrprotoflags(m);
 	m->m_pkthdr.rcvif = ifp;
 	M_SETFIB(m, ifp->if_fib);
+	if (V_encap_hash_inbound)
+		encap_set_flowid(m, af);
 #ifdef MAC
 	mac_ifnet_create_mbuf(ifp, m);
 #endif
diff --git a/sys/net/if_ovpn.c b/sys/net/if_ovpn.c
index 385037268230..4c100037b8d9 100644
--- a/sys/net/if_ovpn.c
+++ b/sys/net/if_ovpn.c
@@ -67,6 +67,7 @@
 #include <netinet/in_fib.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
+#include <netinet/ip_encap.h>
 #include <netinet/ip_var.h>
 #include <netinet/udp.h>
 #include <netinet/udp_var.h>
@@ -1781,6 +1782,8 @@ skip_float:
 	 */
 	af = ovpn_get_af(m);
 	if (af != 0) {
+		if (V_encap_hash_inbound)
+			encap_set_flowid(m, af);
 		BPF_MTAP2(sc->ifp, &af, sizeof(af), m);
 		if (V_async_netisr_queue)
 			netisr_queue(af == AF_INET ? NETISR_IP : NETISR_IPV6, m);
diff --git a/sys/netinet/ip_encap.c b/sys/netinet/ip_encap.c
index d3ca0b19f2f5..b23813620dd4 100644
--- a/sys/netinet/ip_encap.c
+++ b/sys/netinet/ip_encap.c
@@ -71,21 +71,33 @@
 #include <sys/mbuf.h>
 #include <sys/errno.h>
 #include <sys/socket.h>
+#include <sys/sysctl.h>
 
 #include <net/if.h>
 #include <net/if_var.h>
+#include <net/vnet.h>
 
 #include <netinet/in.h>
+#include <netinet/ip.h>
 #include <netinet/ip_var.h>
 #include <netinet/ip_encap.h>
+#include <netinet/in_rss.h>
 
 #ifdef INET6
+#include <netinet/ip6.h>
 #include <netinet6/ip6_var.h>
+#include <netinet6/in6_rss.h>
 #endif
 
 static MALLOC_DEFINE(M_NETADDR, "encap_export_host",
     "Export host address structure");
 
+SYSCTL_DECL(_net_route);
+VNET_DEFINE(bool, encap_hash_inbound) = false;
+SYSCTL_BOOL(_net_route, OID_AUTO, hash_inbound, CTLFLAG_RW | CTLFLAG_VNET,
+    &VNET_NAME(encap_hash_inbound), false,
+    "Compute flowid for packets received over tunnel interfaces");
+
 struct encaptab {
 	CK_LIST_ENTRY(encaptab) chain;
 	int		proto;
@@ -410,3 +422,43 @@ encap6_input(struct mbuf **mp, int *offp, int proto)
 	return (IPPROTO_DONE);
 }
 #endif /* INET6 */
+
+/*
+ * Set the flowid of a decapsulated IP packet from its inner addresses.
+ *
+ * flowid of an incoming packet computed over the outer tunnel tuple,
+ * which is the same for every flow a peer. Hashing the inner addresses
+ * instead to make the mpath (and RSS, if enabled) work properly.
+ *
+ * Callers must check the V_encap_hash_inbound sysctl before calling.
+ */
+bool
+encap_set_flowid(struct mbuf *m, int af)
+{
+	uint32_t rss_hash, rss_type;
+
+	M_ASSERTPKTHDR(m);
+
+	switch (af) {
+#ifdef INET
+	case AF_INET: {
+		if (rss_mbuf_software_hash_v4(m, 0, &rss_hash, &rss_type) != 0)
+			return (false);
+		break;
+	}
+#endif
+#ifdef INET6
+	case AF_INET6: {
+		if (rss_mbuf_software_hash_v6(m, 0, &rss_hash, &rss_type) != 0)
+			return (false);
+		break;
+	}
+#endif
+	default:
+		return (false);
+	}
+
+	m->m_pkthdr.flowid = rss_hash;
+	M_HASHTYPE_SET(m, rss_type);
+	return (true);
+}
diff --git a/sys/netinet/ip_encap.h b/sys/netinet/ip_encap.h
index 1c2488cbfab7..e174b9ac8299 100644
--- a/sys/netinet/ip_encap.h
+++ b/sys/netinet/ip_encap.h
@@ -37,6 +37,8 @@
 
 #ifdef _KERNEL
 
+#include <net/vnet.h>
+
 int	encap4_input(struct mbuf **, int *, int);
 int	encap6_input(struct mbuf **, int *, int);
 
@@ -76,6 +78,11 @@ int ip_encap_unregister_srcaddr(const struct srcaddrtab *);
 int ip6_encap_unregister_srcaddr(const struct srcaddrtab *);
 int ip_encap_detach(const struct encaptab *);
 int ip6_encap_detach(const struct encaptab *);
+
+VNET_DECLARE(bool, encap_hash_inbound);
+#define	V_encap_hash_inbound	VNET(encap_hash_inbound)
+
+bool	encap_set_flowid(struct mbuf *, int);
 #endif
 
 #endif /*_NETINET_IP_ENCAP_H_*/