[PATCH net-next v3 1/3] ipv4: Only lower the pmtu of the other paths of a multipath route

From: Vladimir Vdovin

Date: Fri Nov 13 2026 - 14:27:16 EST


Since commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths
if multipath enabled") __ip_rt_update_pmtu() caches the mtu of a
Fragmentation Needed on every path of a multipath route, as the lookup
there doesn't know the flow the message was sent for and may land on
another path than the one that flow is on. But only that path is
checked against the mtu, by the old_mtu early return, the others get it
as is. A path whose device or route mtu is below it, or that already
learned a lower pmtu, ends up with a pmtu above what it can carry: its
flows send packets of that size, which don't get through, and no
Fragmentation Needed comes back for them until the exception expires.

Skip a path when the mtu would not lower what that path uses now: the
pmtu of its live exception if it has one, else the mtu of the route or
the device, the way ipv4_mtu() would report them. A mtu locked because
it is below ip_rt_min_pmtu is still written everywhere, it clears DF
and has to reach the path the flow is on.

Fixes: 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled")
Signed-off-by: Vladimir Vdovin <deliran@xxxxxxxxxx>
---
net/ipv4/route.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index d0a5f6d77094..f0da009f4115 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -150,6 +150,8 @@ static struct neighbour *ipv4_dst_neigh_lookup(const struct dst_entry *dst,
struct sk_buff *skb,
const void *daddr);
static void ipv4_confirm_neigh(const struct dst_entry *dst, const void *daddr);
+static struct fib_nh_exception *find_exception(struct fib_nh_common *nhc,
+ __be32 daddr);

static struct dst_ops ipv4_dst_ops = {
.family = AF_INET,
@@ -1046,6 +1048,26 @@ out: kfree_skb_reason(skb, reason);
return 0;
}

+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+/* The mtu a path currently uses for daddr, as ipv4_mtu() would report it. */
+static u32 fib_nhc_mtu(struct fib_nh_common *nhc, const struct fib_info *fi,
+ __be32 daddr)
+{
+ struct fib_nh_exception *fnhe = find_exception(nhc, daddr);
+ u32 mtu = 0;
+
+ if (fnhe && fnhe->fnhe_genid == fnhe_genid(dev_net(nhc->nhc_dev)))
+ mtu = fnhe->fnhe_pmtu;
+ if (!mtu)
+ mtu = fi->fib_mtu;
+ if (!mtu)
+ mtu = READ_ONCE(nhc->nhc_dev->mtu);
+ mtu = min(mtu, IP_MAX_MTU);
+
+ return mtu - lwtunnel_headroom(nhc->nhc_lwtstate, mtu);
+}
+#endif
+
static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu)
{
struct dst_entry *dst = &rt->dst;
@@ -1085,6 +1107,14 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu)
nhc = fib_info_nhc(res.fi, nhsel);
if (!nhc)
break;
+ /* old_mtu above is the mtu of the path dst
+ * is on, don't raise what another path
+ * uses. A locked mtu clears DF and goes
+ * to every path.
+ */
+ if (!lock &&
+ mtu > fib_nhc_mtu(nhc, res.fi, fl4->daddr))
+ continue;
update_or_create_fnhe(nhc, fl4->daddr, 0, mtu, lock,
jiffies + net->ipv4.ip_rt_mtu_expires);
}
--
2.56.0