[bug report, net-next] lo.disable_ipv6=1 allows ::1 dst packet to take a default route

From: Sandro Pischinger
Date: Mon Mar 17 2025 - 18:50:18 EST


Hello all,

I stumbled upon an issue when disabling ipv6 just for the loopback device.
Afer doing so, sending a packet with destination ::1 will follow an available
default route, if exists, e.g. "default via fe80::2 dev ens3". This seems
to contradict RFC4291 2.5.3, which states that loopback packets must never leave
the node.

Using mainline kernel v6.14-rc7, configured with
x86_64_defconfig and kvm_guest.config on archlinux (in qemu):

$ ip -6 r
fe80::/64 dev ens3 proto kernel metric 256 pref medium
fec0::/64 dev ens3 proto ra metric 1002 pref medium
multicast ff00::/8 dev ens3 proto kernel metric 256 pref medium
default via fe80::2 dev ens3 proto ra metric 1002 pref medium

$ ip -6 r get ::1
local ::1 dev lo proto kernel src ::1 metric 0 pref medium

$ sysctl -w net.ipv6.conf.lo.disable_ipv6=1
net.ipv6.conf.lo.disable_ipv6 = 1

$ ip -6 r get ::1
::1 via fe80::2 dev ens3 proto ra src fe80::c9cb:505e:82a2:8ca7 metric 1002 pref medium

When observing trace events `fib6_table_lookup` for ping ::1, then we see that the ens3 device is selected
for the case when ipv6 is disabled for lo:
== lo.disable_ipv6=0
ping-356 [000] ..... 292.199930: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..... 292.199932: fib6_table_lookup: table 254 oif 0 iif 1 proto 17 ::/56460 -> ::1/1025 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..... 292.200176: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..... 292.200176: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..s2. 292.200187: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..s2. 292.200187: fib6_table_lookup: table 254 oif 1 iif 1 proto 58 ::1/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..... 293.255592: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..... 293.255594: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..s2. 293.255606: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..s2. 293.255606: fib6_table_lookup: table 254 oif 1 iif 1 proto 58 ::1/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..... 294.279429: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..... 294.279430: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..s2. 294.279441: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..s2. 294.279441: fib6_table_lookup: table 254 oif 1 iif 1 proto 58 ::1/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..... 295.303440: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..... 295.303442: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0
ping-356 [000] ..s2. 295.303451: fib6_table_lookup: (ffffffff84041fd4)
ping-356 [000] ..s2. 295.303452: fib6_table_lookup: table 254 oif 1 iif 1 proto 58 ::1/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev lo gw :: err 0

== lo.disable_ipv6=1
ping-363 [000] ..... 358.104211: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 358.104213: fib6_table_lookup: table 254 oif 0 iif 1 proto 17 ::/37566 -> ::1/1025 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 358.104453: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 358.104453: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 359.111414: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 359.111415: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 360.135436: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 360.135437: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 361.159595: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 361.159597: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 362.183614: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 362.183616: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ping-363 [000] ..... 363.207450: fib6_table_lookup: (ffffffff84041fd4)
ping-363 [000] ..... 363.207452: fib6_table_lookup: table 254 oif 0 iif 1 proto 58 ::/0 -> ::1/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw fe80::2 err 0
ksoftirqd/0-16 [000] ..s.. 363.527470: fib6_table_lookup: (ffffffff84041fd4)
ksoftirqd/0-16 [000] ..s.. 363.527471: fib6_table_lookup: table 254 oif 0 iif 2 proto 58 fe80::2/0 -> fe80::c9cb:505e:82a2:8ca7/0 flowlabel 0 tos 0 scope 0 flags 0 ==> dev ens3 gw :: err 0

Intuitively I would expect `ping ::1` to fail with `ping: connect: Network is unreachable`,
as it does if there's no default route configured. However if there is one,
then ping just continues trying to send packets via the matching default route.

$ ping ::1
PING ::1 (::1) 56 data bytes
^C
::1 ping statistics ---
11 packets transmitted, 0 received, 100% packet loss, time 10230ms

Searching through the netdev mailing list archive, I found a somewaht related discussion,
where special handling for ::1 was mentioned. In particular one comment by Stephen Hemminger was:
as found in
https://lore.kernel.org/netdev/20101216132812.2d7fd885@nehalam/
Message-ID: <20101216132812.2d7fd885@nehalam> :

> When loopback device is being brought down, then keep the route table
> entries because they are special. The entries in the local table for
> linklocal routes and ::1 address should not be purged.

I don't understand enough of kernel IPv6 networking in order to
known if this is (still) the case today.

Is this behavior intended? If not, here's my draft patch for this, though
I'm not familiar with the codebase and cannot foresee any side-effects.

Best regards,
Sandro Pischinger

Signed-off-by: Sandro Pischinger <kernel@xxxxxxxxxxxxxxxxxxx>
---
net/ipv6/route.c | 6 ++++++
1 file changed, 6 insertions(+)

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index fb2e99a56529..b27844de3baa 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -2193,6 +2193,12 @@ int fib6_table_lookup(struct net *net, struct fib6_table *table, int oif,

redo_rt6_select:
rt6_select(net, fn, oif, res, strict);
+ if (ipv6_addr_loopback(&fl6->daddr)) {
+ struct fib6_info *rt = res->f6i;
+
+ if (!rt || !(rt->fib6_flags & RTF_LOCAL))
+ res->f6i = net->ipv6.fib6_null_entry;
+ }
if (res->f6i == net->ipv6.fib6_null_entry) {
fn = fib6_backtrack(fn, &fl6->saddr);
if (fn)
--
2.48.1