Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address
From: Ido Schimmel
Date: Thu Sep 03 2026 - 12:12:41 EST
On Thu, Sep 03, 2026 at 02:10:40PM +0200, Íñigo Huguet wrote:
> Don't allow routing packets with a source address that is not configured
> in the host. Allow it only in certain cases like when using a
> transparent socket, by setting the ANYSRC flag in flowi_flags.
>
> Until now, it was possible to send such a packet if a route can be found
> in the routing table for it. For example:
> 1. Configure an address 1:2::3:4/64 and a static route 1:2::/64
> 2. Establish a TCP connection to 1:2::3:4
> 3. Remove the address from the interface, but keep the route.
> 4. Packets are still sent out by the TCP connection because of
> the static route. No incoming packets are accepted, though.
>
> This patch prevents the outgoing packets to be sent in normal
> circumnstances.
>
> This aligns the behaviour with the IPv4 stack. To determine the places
> where the ANYSRC needs to be set, I set the flag in the same places as
> the IPv4 stack does.
>
> Apart from consolidating the behaviour of both stacks, there is a more
> important reason why this is needed. RFC 4862 states that "an invalid
> address MUST NOT be used as the source address of outbound packets".
> Therefore, sending packets with a source address considered "invalid",
> like an expired address, is disallowed.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Íñigo Huguet <ihuguet@xxxxxxxxxx>
>
> ---
>
> v2:
> - Fix a slab-out-of-bounds bug: in
> tcp_v6_send_response we must not read the inet_flags because it may
> not be an inet_sk, but a request socket.
> Detected by syzbot, Sashiko and other bots.
> - Use the addr_type from saddr instead of daddr in ip6_route_me_harder.
> Detected by Sashiko.
> - Don't overwrite flowi_flags in tcp_v6_connect when setting the
> ANY_SPORT flag. Detected by Sashiko.
> - Fixed line length warnings.
> v1: https://lore.kernel.org/netdev/20260901115021.50057-1-ihuguet@xxxxxxxxxx/
>
> Testing: tested with a manual reproducer executing the steps described
> above. Tested also with transparent sockets to ensure that the packets
> are sent in that case. Also executed the following selftests to prevent
> potential regressions: fcnal-ipv6, fib_tests, fib-onlink-tests,
> nft_nat, nft_tproxy_tcp, nft_tproxy_udp.
>
> The change in the netfilter's ip6_route_me_harder function is the one
> that I'm more unsure about. It was not clear to me the reason why it was
> done like this in the IPv4 counterpart. Please review carefully.
> ---
> drivers/net/vrf.c | 1 +
> net/core/lwt_bpf.c | 1 +
> net/ipv6/af_inet6.c | 1 +
> net/ipv6/datagram.c | 1 +
> net/ipv6/inet6_connection_sock.c | 2 ++
> net/ipv6/ip6_output.c | 28 ++++++++++++++++++++++++++++
> net/ipv6/netfilter.c | 11 +++++++++--
> net/ipv6/ping.c | 1 +
> net/ipv6/raw.c | 1 +
> net/ipv6/syncookies.c | 1 +
> net/ipv6/tcp_ipv6.c | 4 +++-
> net/ipv6/udp.c | 1 +
> net/l2tp/l2tp_ip6.c | 2 ++
> 13 files changed, 52 insertions(+), 3 deletions(-)
1. This is a behavior change, not a bug fix, and should be targeted at
net-next without a Fixes tag.
2. What is the motivation for this drastic change beyond RFC conformance
and parity with IPv4? IMO, these two are not a good enough reason to
make such a change with a huge blast radius.
Here's a recent example of a one line change that argued for IPv4 parity
and was eventually reverted due to regression reports:
https://lore.kernel.org/all/20260104032357.38555-1-yuhuang@xxxxxxxxxx/
https://lore.kernel.org/netdev/20260521135310.GC977@xxxxxxxxxxx/
3. See [1] for a list of regressions that AI flagged. Even if v3 fixes
all of them (which means a much bigger diff), I don't think such a
change will be merged without a proper real-world motivation beyond RFC
conformance and IPv4 parity.
Thanks
[1]
1. Any-IP (local prefix routes) stops working
Setup: ip -6 route add local 2001:db8::/64 dev lo (or the rule + table
form from commit ab79ad14a2d5), TCP listener on [::].
Path: tcp_v6_send_synack() -> inet6_csk_route_req() sets
fl6->saddr = ireq->ir_v6_loc_addr with flags 0 -> ip6_dst_lookup_tail()
-> ipv6_chk_addr_and_flags() misses because the address is only in the
FIB, not in inet6_addr_lst -> -ENETUNREACH.
Same for inet6_csk_route_socket() on the accepted socket,
tcp_v6_send_response() for RSTs, and icmpv6_echo_reply(), which keeps
the incoming daddr as saddr when ipv6_unicast_destination() (RTF_LOCAL)
is true.
Effect: no SYN-ACK, no echo reply, no RST for any Any-IP address.
IPv4 avoids this via the local-table fallback in __ip_dev_find().
2. Anycast source addresses rejected
Anycast addresses live in idev->ac_list, not inet6_addr_lst. Three
paths pick one deliberately:
- icmp6_send() uses ipv6_chk_acast_addr_src() to source ICMPv6 errors
from the anycast daddr of the offending packet.
- icmpv6_echo_reply() with anycast_src_echo_reply=1.
- ip6_datagram_send_ctl() accepts an anycast IPV6_PKTINFO source
(commit 7c90cc2d40ca), then udpv6_sendmsg() ->
ip6_sk_dst_lookup_flow() fails it.
Effect: ICMPv6 errors and echo replies for anycast destinations are
dropped with OUTNOROUTES incremented. UDP sendmsg() with an anycast
pktinfo passes the ancillary-data check and then fails with
-ENETUNREACH.
3. TIME_WAIT replies of IP_TRANSPARENT connections dropped
tcp_v6_send_response() uses "sk && sk_fullsock(sk)" to decide the
flags. tcp_v6_rcv() reaches it with a timewait socket for both
TCP_TW_ACK (tcp_v6_timewait_ack() -> tcp_v6_send_ack()) and TCP_TW_RST
(tcp_v6_send_reset()). sk_fullsock() is false there, flags are 0, and
fl6.saddr is the proxied non-local address.
Effect: tproxy'd IPv6 connections send no ACK or RST from TIME_WAIT.
IPv4 uses inet_sk_transparent(), which reads tw->tw_transparent and
ireq->no_srccheck.
4. BPF-set non-local IPv6 tunnel sources stop working
bpf_skb_set_tunnel_key() sets key.flow_flags = FLOWI_FLAG_ANYSRC only
in the IPv4 branch (commit b8fff748521c, added so a program can use
e.g. a container address as the outer source). udp_tunnel6_dst_lookup()
(vxlan, geneve, bareudp) copies key->u.ipv6.src into fl6.saddr and
never copies key->flow_flags. Before the patch this did not matter
because IPv6 never checked the source.
Effect: IPv6 collect_md tunnels with a BPF-chosen non-local source get
-ENETUNREACH while the IPv4 equivalent keeps working.
5. ICMPv6 errors under IPsec lost in the relookup path
icmpv6_route_lookup() does a second ip6_dst_lookup() with fl2 from
xfrm_decode_session_reverse() when the first xfrm_lookup() returned
-EPERM. fl2.saddr is the daddr of the packet in error, which is a
remote host when the packet was being forwarded through a gateway. The
new check fails it, relookup_failed has dst == NULL in the -EPERM case,
and the function returns ERR_PTR(-ENETUNREACH).
Effect: RFC 4301 ICMP handling on IPsec gateways with a block policy no
longer sends the error into the tunnel. icmp_route_lookup() sets
fl4_2.flowi4_flags |= FLOWI_FLAG_ANYSRC for exactly this relookup.