* [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route
@ 2026-10-17 21:05 Vladimir Vdovin
2026-10-17 21:05 ` [PATCH net-next 1/2] ipv6: Cache pmtu for " Vladimir Vdovin
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Vladimir Vdovin @ 2026-10-17 21:05 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kselftest, linux-kernel, Vladimir Vdovin
IPv6 counterpart of commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for
all packet paths if multipath enabled"), deferred back then:
https://lore.kernel.org/netdev/ad29493c-9e48-4a94-b327-e47af3a9d137@kernel.org/
Patch 1 creates the PMTU exception on every path of a multipath route,
patch 2 adds the IPv6 variant of the pmtu.sh multipath test, which
fails without patch 1.
Vladimir Vdovin (2):
ipv6: Cache pmtu for all paths of a multipath route
selftests: net: pmtu: Add IPv6 multipath exceptions test
net/ipv6/route.c | 88 ++++++++++-----
tools/testing/selftests/net/pmtu.sh | 162 +++++++++++++++++++++-------
2 files changed, 185 insertions(+), 65 deletions(-)
base-commit: 98c2ad4f5d805dbc1ea436a334ee59311806a48c
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net-next 1/2] ipv6: Cache pmtu for all paths of a multipath route
2026-10-17 21:05 [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Vladimir Vdovin
@ 2026-10-17 21:05 ` Vladimir Vdovin
2026-10-18 22:06 ` netdev-bot+sashiko
2026-10-17 21:05 ` [PATCH net-next 2/2] selftests: net: pmtu: Add IPv6 multipath exceptions test Vladimir Vdovin
2026-10-18 13:47 ` [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Ido Schimmel
2 siblings, 1 reply; 5+ messages in thread
From: Vladimir Vdovin @ 2026-10-17 21:05 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kselftest, linux-kernel, Vladimir Vdovin
__ip6_rt_update_pmtu() creates the exception route only on the nexthop
the Packet Too Big came in through. With a multipath route the other
paths never see that ICMP error and keep sending with the link MTU, so
flows hashed to them blackhole.
Same problem and same fix as for IPv4 in commit 7d3f3b4367f3 ("net:
ipv4: Cache pmtu for all packet paths if multipath enabled"): create
the exception on every path. For a nexthop object walk all of its
fib6_nh, for a legacy multipath route walk the siblings of the selected
fib6_info.
Link: https://lore.kernel.org/netdev/ad29493c-9e48-4a94-b327-e47af3a9d137@kernel.org/
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
net/ipv6/route.c | 88 +++++++++++++++++++++++++++++++++---------------
1 file changed, 61 insertions(+), 27 deletions(-)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 34ab7f330540..d870c20f2728 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -2888,6 +2888,30 @@ static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt)
(rt->rt6i_flags & RTF_PCPU || rcu_access_pointer(rt->from));
}
+struct rt6_pmtu_exception_arg {
+ struct fib6_result *res;
+ const struct in6_addr *daddr;
+ const struct in6_addr *saddr;
+ u32 mtu;
+};
+
+static int rt6_nh_insert_pmtu_exception(struct fib6_nh *nh, void *_arg)
+{
+ struct rt6_pmtu_exception_arg *arg = _arg;
+ struct fib6_result *res = arg->res;
+ struct rt6_info *nrt6;
+
+ res->nh = nh;
+ nrt6 = ip6_rt_cache_alloc(res, arg->daddr, arg->saddr);
+ if (nrt6) {
+ rt6_do_update_pmtu(nrt6, arg->mtu);
+ if (rt6_insert_exception(nrt6, res))
+ dst_release_immediate(&nrt6->dst);
+ }
+
+ return 0;
+}
+
static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
const struct ipv6hdr *iph, u32 mtu,
bool confirm_neigh)
@@ -2925,42 +2949,52 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
if (rt6->rt6i_flags & RTF_CACHE)
rt6_update_exception_stamp_rt(rt6);
} else if (daddr) {
+ struct fib6_info *f6i, *sibling;
struct fib6_result res = {};
- struct rt6_info *nrt6;
+ struct rt6_pmtu_exception_arg arg = {
+ .res = &res,
+ .daddr = daddr,
+ .saddr = saddr,
+ .mtu = mtu,
+ };
rcu_read_lock();
- res.f6i = rcu_dereference(rt6->from);
- if (!res.f6i)
+ f6i = rcu_dereference(rt6->from);
+ if (!f6i)
goto out_unlock;
- res.fib6_flags = res.f6i->fib6_flags;
- res.fib6_type = res.f6i->fib6_type;
+ res.f6i = f6i;
+ res.fib6_flags = f6i->fib6_flags;
+ res.fib6_type = f6i->fib6_type;
- if (res.f6i->nh) {
- struct fib6_nh_match_arg arg = {
- .dev = dst_dev_rcu(dst),
- .gw = &rt6->rt6i_gateway,
- };
-
- nexthop_for_each_fib6_nh(res.f6i->nh,
- fib6_nh_find_match, &arg);
+ /* A Packet Too Big only ever arrives through the path the
+ * offending packet took, so cache the pmtu on every path of a
+ * multipath route: the other paths will not get their own
+ * ICMP error and would keep sending with the link MTU.
+ */
+ if (f6i->nh) {
+ nexthop_for_each_fib6_nh(f6i->nh,
+ rt6_nh_insert_pmtu_exception,
+ &arg);
+ goto out_unlock;
+ }
- /* fib6_info uses a nexthop that does not have fib6_nh
- * using the dst->dev + gw. Should be impossible.
- */
- if (!arg.match)
- goto out_unlock;
+ rt6_nh_insert_pmtu_exception(f6i->fib6_nh, &arg);
- res.nh = arg.match;
- } else {
- res.nh = res.f6i->fib6_nh;
- }
+ if (!READ_ONCE(f6i->fib6_nsiblings))
+ goto out_unlock;
- nrt6 = ip6_rt_cache_alloc(&res, daddr, saddr);
- if (nrt6) {
- rt6_do_update_pmtu(nrt6, mtu);
- if (rt6_insert_exception(nrt6, &res))
- dst_release_immediate(&nrt6->dst);
+ /* fib6_del_route() zeroes fib6_nsiblings before unlinking
+ * f6i, stop then as its list_head may no longer be reached.
+ */
+ list_for_each_entry_rcu(sibling, &f6i->fib6_siblings,
+ fib6_siblings) {
+ res.f6i = sibling;
+ res.fib6_flags = sibling->fib6_flags;
+ res.fib6_type = sibling->fib6_type;
+ rt6_nh_insert_pmtu_exception(sibling->fib6_nh, &arg);
+ if (!READ_ONCE(f6i->fib6_nsiblings))
+ break;
}
out_unlock:
rcu_read_unlock();
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net-next 2/2] selftests: net: pmtu: Add IPv6 multipath exceptions test
2026-10-17 21:05 [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Vladimir Vdovin
2026-10-17 21:05 ` [PATCH net-next 1/2] ipv6: Cache pmtu for " Vladimir Vdovin
@ 2026-10-17 21:05 ` Vladimir Vdovin
2026-10-18 13:47 ` [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Ido Schimmel
2 siblings, 0 replies; 5+ messages in thread
From: Vladimir Vdovin @ 2026-10-17 21:05 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kselftest, linux-kernel, Vladimir Vdovin
Make pmtu_ipv4_mp_exceptions family agnostic and add
pmtu_ipv6_mp_exceptions: one oversized ping over a multipath route
must leave PMTU exceptions on both paths.
Without the preceding fix the IPv6 test fails with both legacy
nexthops and nexthop objects:
PMTU exception wasn't created after exceeding MTU (veth_A-R2)
Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
---
tools/testing/selftests/net/pmtu.sh | 162 +++++++++++++++++++++-------
1 file changed, 124 insertions(+), 38 deletions(-)
diff --git a/tools/testing/selftests/net/pmtu.sh b/tools/testing/selftests/net/pmtu.sh
index c7cd271714ef..9ff46708fa03 100755
--- a/tools/testing/selftests/net/pmtu.sh
+++ b/tools/testing/selftests/net/pmtu.sh
@@ -203,6 +203,9 @@
# on host A and B on lo reachable via both routers. Host A and B
# addresses have multipath routes to each other, b_r1 mtu = 1500.
# Check that PMTU exceptions are created for both paths.
+#
+# - pmtu_ipv6_mp_exceptions
+# Same as above but with IPv6
source lib.sh
@@ -272,7 +275,8 @@ tests="
list_flush_ipv6_exception ipv6: list and flush cached exceptions 1
pmtu_ipv4_route_change ipv4: PMTU exception w/route replace 1
pmtu_ipv6_route_change ipv6: PMTU exception w/route replace 1
- pmtu_ipv4_mp_exceptions ipv4: PMTU multipath nh exceptions 1"
+ pmtu_ipv4_mp_exceptions ipv4: PMTU multipath nh exceptions 1
+ pmtu_ipv6_mp_exceptions ipv6: PMTU multipath nh exceptions 1"
# Addressing and routing for tests with routers: four network segments, with
# index SEGMENT between 1 and 4, a common prefix (PREFIX4 or PREFIX6) and an
@@ -352,6 +356,9 @@ tunnel6_mask="64"
host4_a_addr="192.168.99.99"
host4_b_addr="192.168.88.88"
+host6_a_addr="fc00:1000::a"
+host6_b_addr="fc00:1001::b"
+
dummy6_0_prefix="fc00:1000::"
dummy6_1_prefix="fc00:1001::"
dummy6_mask="64"
@@ -994,49 +1001,112 @@ setup_ovs_bridge() {
}
setup_multipath_new() {
- # Set up host A with multipath routes to host B host4_b_addr
- run_cmd ${ns_a} ip addr add ${host4_a_addr} dev lo
- run_cmd ${ns_a} ip nexthop add id 401 via ${prefix4}.${a_r1}.2 dev veth_A-R1
- run_cmd ${ns_a} ip nexthop add id 402 via ${prefix4}.${a_r2}.2 dev veth_A-R2
- run_cmd ${ns_a} ip nexthop add id 403 group 401/402
- run_cmd ${ns_a} ip route add ${host4_b_addr} src ${host4_a_addr} nhid 403
+ family=${1}
- # Set up host B with multipath routes to host A host4_a_addr
- run_cmd ${ns_b} ip addr add ${host4_b_addr} dev lo
- run_cmd ${ns_b} ip nexthop add id 401 via ${prefix4}.${b_r1}.2 dev veth_B-R1
- run_cmd ${ns_b} ip nexthop add id 402 via ${prefix4}.${b_r2}.2 dev veth_B-R2
- run_cmd ${ns_b} ip nexthop add id 403 group 401/402
- run_cmd ${ns_b} ip route add ${host4_a_addr} src ${host4_b_addr} nhid 403
+ if [ ${family} -eq 4 ]; then
+ host_a_addr=${host4_a_addr}
+ host_b_addr=${host4_b_addr}
+ nh_a_r1=${prefix4}.${a_r1}.2
+ nh_a_r2=${prefix4}.${a_r2}.2
+ nh_b_r1=${prefix4}.${b_r1}.2
+ nh_b_r2=${prefix4}.${b_r2}.2
+ else
+ host_a_addr=${host6_a_addr}
+ host_b_addr=${host6_b_addr}
+ nh_a_r1=${prefix6}:${a_r1}::2
+ nh_a_r2=${prefix6}:${a_r2}::2
+ nh_b_r1=${prefix6}:${b_r1}::2
+ nh_b_r2=${prefix6}:${b_r2}::2
+ fi
+
+ # Set up host A with multipath routes to host B host_b_addr
+ run_cmd ${ns_a} ip addr add ${host_a_addr} dev lo
+ run_cmd ${ns_a} ip nexthop add id ${family}01 via ${nh_a_r1} dev veth_A-R1
+ run_cmd ${ns_a} ip nexthop add id ${family}02 via ${nh_a_r2} dev veth_A-R2
+ run_cmd ${ns_a} ip nexthop add id ${family}03 group ${family}01/${family}02
+ run_cmd ${ns_a} ip route add ${host_b_addr} src ${host_a_addr} nhid ${family}03
+
+ # Set up host B with multipath routes to host A host_a_addr
+ run_cmd ${ns_b} ip addr add ${host_b_addr} dev lo
+ run_cmd ${ns_b} ip nexthop add id ${family}01 via ${nh_b_r1} dev veth_B-R1
+ run_cmd ${ns_b} ip nexthop add id ${family}02 via ${nh_b_r2} dev veth_B-R2
+ run_cmd ${ns_b} ip nexthop add id ${family}03 group ${family}01/${family}02
+ run_cmd ${ns_b} ip route add ${host_a_addr} src ${host_b_addr} nhid ${family}03
}
setup_multipath_old() {
- # Set up host A with multipath routes to host B host4_b_addr
- run_cmd ${ns_a} ip addr add ${host4_a_addr} dev lo
- run_cmd ${ns_a} ip route add ${host4_b_addr} \
- src ${host4_a_addr} \
- nexthop via ${prefix4}.${a_r1}.2 weight 1 \
- nexthop via ${prefix4}.${a_r2}.2 weight 1
+ family=${1}
- # Set up host B with multipath routes to host A host4_a_addr
- run_cmd ${ns_b} ip addr add ${host4_b_addr} dev lo
- run_cmd ${ns_b} ip route add ${host4_a_addr} \
- src ${host4_b_addr} \
- nexthop via ${prefix4}.${b_r1}.2 weight 1 \
- nexthop via ${prefix4}.${b_r2}.2 weight 1
+ if [ ${family} -eq 4 ]; then
+ host_a_addr=${host4_a_addr}
+ host_b_addr=${host4_b_addr}
+ nh_a_r1=${prefix4}.${a_r1}.2
+ nh_a_r2=${prefix4}.${a_r2}.2
+ nh_b_r1=${prefix4}.${b_r1}.2
+ nh_b_r2=${prefix4}.${b_r2}.2
+ else
+ host_a_addr=${host6_a_addr}
+ host_b_addr=${host6_b_addr}
+ nh_a_r1=${prefix6}:${a_r1}::2
+ nh_a_r2=${prefix6}:${a_r2}::2
+ nh_b_r1=${prefix6}:${b_r1}::2
+ nh_b_r2=${prefix6}:${b_r2}::2
+ fi
+
+ # Set up host A with multipath routes to host B host_b_addr
+ run_cmd ${ns_a} ip addr add ${host_a_addr} dev lo
+ run_cmd ${ns_a} ip route add ${host_b_addr} \
+ src ${host_a_addr} \
+ nexthop via ${nh_a_r1} weight 1 \
+ nexthop via ${nh_a_r2} weight 1
+
+ # Set up host B with multipath routes to host A host_a_addr
+ run_cmd ${ns_b} ip addr add ${host_b_addr} dev lo
+ run_cmd ${ns_b} ip route add ${host_a_addr} \
+ src ${host_b_addr} \
+ nexthop via ${nh_b_r1} weight 1 \
+ nexthop via ${nh_b_r2} weight 1
}
+
setup_multipath() {
+ family="$1"
+
+ if [ ${family} -eq 4 ]; then
+ host_a_addr=${host4_a_addr}
+ host_b_addr=${host4_b_addr}
+ nh_r1_a=${prefix4}.${a_r1}.1
+ nh_r1_b=${prefix4}.${b_r1}.1
+ nh_r2_a=${prefix4}.${a_r2}.1
+ nh_r2_b=${prefix4}.${b_r2}.1
+ else
+ host_a_addr=${host6_a_addr}
+ host_b_addr=${host6_b_addr}
+ nh_r1_a=${prefix6}:${a_r1}::1
+ nh_r1_b=${prefix6}:${b_r1}::1
+ nh_r2_a=${prefix6}:${a_r2}::1
+ nh_r2_b=${prefix6}:${b_r2}::1
+ fi
+
if [ "$USE_NH" = "yes" ]; then
- setup_multipath_new
+ setup_multipath_new ${family}
else
- setup_multipath_old
+ setup_multipath_old ${family}
fi
# Set up routers with routes to dummies
- run_cmd ${ns_r1} ip route add ${host4_a_addr} via ${prefix4}.${a_r1}.1
- run_cmd ${ns_r2} ip route add ${host4_a_addr} via ${prefix4}.${a_r2}.1
- run_cmd ${ns_r1} ip route add ${host4_b_addr} via ${prefix4}.${b_r1}.1
- run_cmd ${ns_r2} ip route add ${host4_b_addr} via ${prefix4}.${b_r2}.1
+ run_cmd ${ns_r1} ip route add ${host_a_addr} via ${nh_r1_a}
+ run_cmd ${ns_r2} ip route add ${host_a_addr} via ${nh_r2_a}
+ run_cmd ${ns_r1} ip route add ${host_b_addr} via ${nh_r1_b}
+ run_cmd ${ns_r2} ip route add ${host_b_addr} via ${nh_r2_b}
+}
+
+setup_multipath4() {
+ setup_multipath 4
+}
+
+setup_multipath6() {
+ setup_multipath 6
}
setup() {
@@ -2377,8 +2447,18 @@ test_pmtu_ipv6_route_change() {
test_pmtu_ipvX_route_change 6
}
-test_pmtu_ipv4_mp_exceptions() {
- setup namespaces routing multipath || return $ksft_skip
+test_pmtu_ipvX_mp_exceptions() {
+ family=${1}
+
+ if [ ${family} -eq 4 ]; then
+ ping=ping
+ dst="${host4_b_addr}"
+ else
+ ping=${ping6}
+ dst="${host6_b_addr}"
+ fi
+
+ setup namespaces routing multipath${family} || return $ksft_skip
trace "${ns_a}" veth_A-R1 "${ns_r1}" veth_R1-A \
"${ns_r1}" veth_R1-B "${ns_b}" veth_B-R1 \
@@ -2395,18 +2475,24 @@ test_pmtu_ipv4_mp_exceptions() {
mtu "${ns_r2}" veth_R2-A 2000
mtu "${ns_r2}" veth_R2-B 1500
mtu "${ns_b}" veth_B-R2 1500
-
# Ping and expect two nexthop exceptions for two routes
- run_cmd ${ns_a} ping -q -M want -i 0.1 -c 1 -s 1800 "${host4_b_addr}"
+ run_cmd ${ns_a} ${ping} -q -M want -i 0.1 -c 1 -s 1800 "${dst}"
# Check that exceptions have been created with the correct PMTU
- pmtu_a_R1="$(route_get_dst_pmtu_from_exception "${ns_a}" "${host4_b_addr}" oif veth_A-R1)"
- pmtu_a_R2="$(route_get_dst_pmtu_from_exception "${ns_a}" "${host4_b_addr}" oif veth_A-R2)"
-
+ pmtu_a_R1="$(route_get_dst_pmtu_from_exception "${ns_a}" "${dst}" oif veth_A-R1)"
+ pmtu_a_R2="$(route_get_dst_pmtu_from_exception "${ns_a}" "${dst}" oif veth_A-R2)"
check_pmtu_value "1500" "${pmtu_a_R1}" "exceeding MTU (veth_A-R1)" || return 1
check_pmtu_value "1500" "${pmtu_a_R2}" "exceeding MTU (veth_A-R2)" || return 1
}
+test_pmtu_ipv4_mp_exceptions() {
+ test_pmtu_ipvX_mp_exceptions 4
+}
+
+test_pmtu_ipv6_mp_exceptions() {
+ test_pmtu_ipvX_mp_exceptions 6
+}
+
usage() {
echo
echo "$0 [OPTIONS] [TEST]..."
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route
2026-10-17 21:05 [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Vladimir Vdovin
2026-10-17 21:05 ` [PATCH net-next 1/2] ipv6: Cache pmtu for " Vladimir Vdovin
2026-10-17 21:05 ` [PATCH net-next 2/2] selftests: net: pmtu: Add IPv6 multipath exceptions test Vladimir Vdovin
@ 2026-10-18 13:47 ` Ido Schimmel
2 siblings, 0 replies; 5+ messages in thread
From: Ido Schimmel @ 2026-10-18 13:47 UTC (permalink / raw)
To: Vladimir Vdovin
Cc: netdev, dsahern, davem, edumazet, kuba, pabeni, horms, shuah,
linux-kselftest, linux-kernel
On Sun, Oct 18, 2026 at 12:05:06AM +0300, Vladimir Vdovin wrote:
> IPv6 counterpart of commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for
> all packet paths if multipath enabled"), deferred back then:
> https://lore.kernel.org/netdev/ad29493c-9e48-4a94-b327-e47af3a9d137@kernel.org/
[...]
> net/ipv6/route.c | 88 ++++++++++-----
Given that this was deferred for two years, I would prefer not to rush
it in on the last day of the cycle. Please repost when net-next reopens
after the merge window.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net-next 1/2] ipv6: Cache pmtu for all paths of a multipath route
2026-10-17 21:05 ` [PATCH net-next 1/2] ipv6: Cache pmtu for " Vladimir Vdovin
@ 2026-10-18 22:06 ` netdev-bot+sashiko
0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-10-18 22:06 UTC (permalink / raw)
To: deliran
Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
shuah, linux-kselftest, linux-kernel
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch fans out to all paths only on the first Packet Too
Big.
- [Medium] A PTB on one path now replaces whatever exception the other
paths already hold for the same daddr, even when that exception has a…
- [Medium] The new legacy-sibling walk in __ip6_rt_update_pmtu()
(route.c:2984-2998) relies only on f6i->fib6_nsiblings to decide…
- [Low] The new comment in __ip6_rt_update_pmtu() (route.c:2970-2974)
says 'A Packet Too Big only ever arrives through the path the…
- [Low] The commit message has no Fixes: tag and does not say which tree
it targets.
--
ipv6: Cache pmtu for all paths of a multipath route
This patch makes __ip6_rt_update_pmtu() create a PMTU exception on every
path of a multipath route, not only on the selected one. For nexthop
objects it walks all fib6_nh entries. For legacy multipath routes it walks
the fib6_siblings list of the selected fib6_info, mirroring IPv4 commit
7d3f3b4367f3.
Link: https://lore.kernel.org/netdev/ad29493c-9e48-4a94-b327-e47af3a9d137@kernel.org/
> Link: https://lore.kernel.org/netdev/ad29493c-9e48-4a94-b327-e47af3a9d137@kernel.org/
> Signed-off-by: Vladimir Vdovin <deliran@verdict.gg>
[Severity: Low]
This isn't a bug, but there is no Fixes: tag. The IPv4 counterpart
7d3f3b4367f3 was also applied without one, so this may be fine for netdev.
It is only worth adding if a stable backport is intended. The target tree
would go in the subject prefix.
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index 34ab7f330540c..d870c20f27281 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -2888,6 +2888,30 @@ static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt)
> (rt->rt6i_flags & RTF_PCPU || rcu_access_pointer(rt->from));
> }
>
[ ... ]
> +static int rt6_nh_insert_pmtu_exception(struct fib6_nh *nh, void *_arg)
> +{
> + struct rt6_pmtu_exception_arg *arg = _arg;
> + struct fib6_result *res = arg->res;
> + struct rt6_info *nrt6;
> +
> + res->nh = nh;
> + nrt6 = ip6_rt_cache_alloc(res, arg->daddr, arg->saddr);
> + if (nrt6) {
> + rt6_do_update_pmtu(nrt6, arg->mtu);
> + if (rt6_insert_exception(nrt6, res))
> + dst_release_immediate(&nrt6->dst);
[Severity: Medium]
Can a PTB on one path now overwrite a lower PMTU or a redirect that
another path has already learned?
In __ip6_rt_update_pmtu() the only size check is mtu >= dst6_mtu(dst),
and it uses the current path's dst. rt6_insert_exception() then compares
the new clone only with fib6_mtu(res), and it removes any existing
exception for the same key:
if (dst_metric_raw(&nrt->dst, RTAX_MTU) >= fib6_mtu(res)) {
err = -EINVAL;
goto out;
}
rt6_ex = __rt6_find_exception_spinlock(&bucket, &nrt->rt6i_dst.addr,
src_key);
if (rt6_ex)
rt6_remove_exception(bucket, rt6_ex);
So there are two cases.
Path B has learned PMTU 1280, and path A has no exception yet (or it
expired or was evicted). A PTB of 1400 on A replaces B's 1280 exception
with a 1400 one. B's flows then blackhole until B gets its own PTB.
Path B has a redirect exception from rt6_do_redirect(), with rt6i_gateway
set to the redirect target. A PTB on A replaces it with a PMTU-only clone
that uses B's original gateway, so the redirect is lost.
IPv4's update_or_create_fnhe() also overwrites fnhe_pmtu, but it keeps
fnhe_gw when only a pmtu is passed. Before this patch, a PTB on one path
never touched the exceptions of other paths.
Should rt6_nh_insert_pmtu_exception() leave an existing exception on that
nexthop alone if it already has a lower MTU or is a redirect?
> + }
> +
> + return 0;
> +}
> +
> static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
> const struct ipv6hdr *iph, u32 mtu,
> bool confirm_neigh)
> @@ -2925,42 +2949,52 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
> if (rt6->rt6i_flags & RTF_CACHE)
> rt6_update_exception_stamp_rt(rt6);
> } else if (daddr) {
[Severity: High]
Does the fan-out to all paths only happen for the first Packet Too Big?
The new code is only in this else branch, which is reached only when
rt6_cache_allowed_for_pmtu() is true:
return !(rt->rt6i_flags & RTF_CACHE) &&
(rt->rt6i_flags & RTF_PCPU || rcu_access_pointer(rt->from));
Once the first PTB has created exceptions, the next lookup through
ip6_pol_route()->rt6_find_cached_rt() returns the RTF_CACHE exception for
the hashed path. This covers both the ip6_route_output() call in
ip6_update_pmtu() and TCP's re-route.
A second, lower PTB then takes the first branch, for example from a
further bottleneck or after a path change:
if (!rt6_cache_allowed_for_pmtu(rt6)) {
rt6_do_update_pmtu(rt6, mtu);
/* update rt6_ex->stamp for cache */
if (rt6->rt6i_flags & RTF_CACHE)
rt6_update_exception_stamp_rt(rt6);
That branch updates only this one exception. Wouldn't the exceptions on
the other siblings or nexthop group members keep the older, higher MTU?
Flows hashed to them would then blackhole again.
The expiry times also drift apart. The lowered path's exception now
expires later than the others. When the others expire, those paths go
back to the link MTU. Meanwhile, PTBs looked up through the still-cached
path keep taking the single-path branch.
IPv4's __ip_rt_update_pmtu() doesn't have this split. Whenever
fib_info_num_path() > 1 it calls fib_lookup() and then
update_or_create_fnhe() on every nhsel, whether or not the current dst is
already a cached fnhe route.
Should the RTF_CACHE branch also update every path?
> + struct fib6_info *f6i, *sibling;
[ ... ]
> + /* A Packet Too Big only ever arrives through the path the
> + * offending packet took, so cache the pmtu on every path of a
> + * multipath route: the other paths will not get their own
> + * ICMP error and would keep sending with the link MTU.
> + */
[Severity: Low]
Is this comment accurate? The interface the ICMPv6 error arrives on isn't
used here. The exception goes on the nexthop of the dst passed to
->update_pmtu.
For TCP, that dst comes from inet6_csk_route_socket(), which sets the
protocol, ports and multipath hash:
fl6->flowi6_proto = sk->sk_protocol;
...
if (sk->sk_protocol == IPPROTO_TCP)
ip6_ecmp_set_mp_hash(sock_net(sk), fl6, sk->sk_txhash);
...
fl6->fl6_sport = inet->inet_sport;
fl6->fl6_dport = inet->inet_dport;
So each TCP flow's PTB already lands on that flow's own transmit path.
The cross-path mismatch seems to come from ip6_update_pmtu(), which UDP,
raw, ping and tunnels use. Its flowi6 has no flowi6_proto and no ports:
struct flowi6 fl6 = {
.flowi6_oif = oif,
.flowi6_mark = mark ? mark : IP6_REPLY_MARK(net, skb->mark),
.daddr = iph->daddr,
.saddr = iph->saddr,
.flowlabel = ip6_flowinfo(iph),
.flowi6_uid = uid,
};
rt6_multipath_hash() uses fl6->flowi6_proto even with policy 0, so the
path chosen here can differ from the transmit path.
The comment also seems to contradict itself. If every PTB came back over
the offending packet's own path, every path would get its own PTB. The
rationale also differs from the one in IPv4 commit 7d3f3b4367f3.
The commit message uses the same wording:
__ip6_rt_update_pmtu() creates the exception route only on the nexthop
the Packet Too Big came in through.
Could the comment and changelog describe the actual mechanism? They could
also mention that TCP flows, which were already handled per path, now get
one path's PMTU applied to all paths.
> + if (f6i->nh) {
[ ... ]
> + /* fib6_del_route() zeroes fib6_nsiblings before unlinking
> + * f6i, stop then as its list_head may no longer be reached.
> + */
> + list_for_each_entry_rcu(sibling, &f6i->fib6_siblings,
> + fib6_siblings) {
> + res.f6i = sibling;
> + res.fib6_flags = sibling->fib6_flags;
> + res.fib6_type = sibling->fib6_type;
> + rt6_nh_insert_pmtu_exception(sibling->fib6_nh, &arg);
> + if (!READ_ONCE(f6i->fib6_nsiblings))
> + break;
> }
[Severity: Medium]
Is fib6_nsiblings enough to tell whether the fib6_siblings ring of f6i is
still safe to walk? Only fib6_del_route() clears it.
The ECMP NLM_F_REPLACE path in fib6_add_rt2node() unlinks the old leaf and
its siblings from the node chain and queues them on purge_list. It does
not call list_del_rcu() on their fib6_siblings or zero fib6_nsiblings:
nsiblings = iter->fib6_nsiblings;
iter->fib6_node = NULL;
list_add(&iter->purge_link, purge_list);
fib6_add() then calls fib6_purge_rt() and fib6_info_release() on each
entry. list_add() puts the siblings ahead of the original leaf A, so the
siblings are released before fib6_purge_rt(A) clears from on A's per-CPU
dsts.
In that window, could a PTB handler do the following?
rcu_read_lock()
f6i = rcu_dereference(rt6->from) /* still A */
READ_ONCE(f6i->fib6_nsiblings) /* still non-zero */
list_for_each_entry_rcu(sibling, &f6i->fib6_siblings, ...)
sibling->fib6_flags, sibling->fib6_nh /* B or C, already released */
If the reader's critical section started after call_rcu() on B or C, they
can be freed while it runs. That would be a use-after-free of the
fib6_info.
rt6_nh_insert_pmtu_exception() always returns 0, so the -ENOENT from the
fib6_destroying check in rt6_insert_exception() doesn't stop the walk
either.
The window is narrow: it needs an admin route replace to race with PTB
processing, and I didn't find a deterministic trigger. rt6_fill_node()
already has a similar sibling walk anchored on dst->from. This one,
though, can be reached from a remote ICMPv6 PTB.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261017210508.71849-1-deliran%40verdict.gg
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-18 22:06 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-17 21:05 [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Vladimir Vdovin
2026-10-17 21:05 ` [PATCH net-next 1/2] ipv6: Cache pmtu for " Vladimir Vdovin
2026-10-18 22:06 ` netdev-bot+sashiko
2026-10-17 21:05 ` [PATCH net-next 2/2] selftests: net: pmtu: Add IPv6 multipath exceptions test Vladimir Vdovin
2026-10-18 13:47 ` [PATCH net-next 0/2] ipv6: Cache pmtu on all paths of a multipath route Ido Schimmel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®