* [PATCH net v2] net/ipv6: don't route packets with unknown source address
@ 2026-09-03 12:10 Íñigo Huguet
2026-09-03 14:56 ` Ido Schimmel
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Íñigo Huguet @ 2026-09-03 12:10 UTC (permalink / raw)
To: David Ahern, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Neal Cardwell, Ido Schimmel,
Pablo Neira Ayuso, Florian Westphal, Willem de Bruijn
Cc: dcaratti, ihuguet, Íñigo Huguet, Simon Horman,
Kuniyuki Iwashima, Phil Sutter, Daniel Borkmann, Junseo Lim,
Martin KaFai Lau, Xuanqiang Luo, Fernando Fernandez Mancera,
Leon Hwang, Willem de Bruijn, Kees Cook, Jeff Layton,
Christian Brauner, Qi Tang, Joe Damato, Breno Leitao, Li RongQing,
open list:VRF, open list, open list:NETFILTER,
open list:NETFILTER,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_)
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@riseup.net>
---
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@riseup.net/
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(-)
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index a0557a3a7026..6c09d5f46d6b 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -432,6 +432,7 @@ static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
fl6.flowlabel = ip6_flowinfo(iph);
fl6.flowi6_mark = skb->mark;
fl6.flowi6_proto = iph->nexthdr;
+ fl6.flowi6_flags = FLOWI_FLAG_ANYSRC;
dst = ip6_dst_lookup_flow(net, NULL, &fl6, NULL);
if (IS_ERR(dst) || dst == dst_null)
diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c
index da49364ec63d..e8b954282689 100644
--- a/net/core/lwt_bpf.c
+++ b/net/core/lwt_bpf.c
@@ -233,6 +233,7 @@ static int bpf_lwt_xmit_reroute(struct sk_buff *skb)
fl6.flowi6_mark = skb->mark;
fl6.flowi6_uid = sock_net_uid(net, sk);
fl6.flowlabel = ip6_flowinfo(iph6);
+ fl6.flowi6_flags = FLOWI_FLAG_ANYSRC;
fl6.flowi6_proto = iph6->nexthdr;
fl6.daddr = iph6->daddr;
fl6.saddr = iph6->saddr;
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 282912a11999..e55a36372ab2 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -820,6 +820,7 @@ int inet6_sk_rebuild_header(struct sock *sk)
fl6->fl6_dport = inet->inet_dport;
fl6->fl6_sport = inet->inet_sport;
fl6->flowi6_uid = sk_uid(sk);
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
security_sk_classify_flow(sk, flowi6_to_flowi_common(fl6));
ip6_ecmp_set_mp_hash(sock_net(sk), fl6, sk->sk_txhash);
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 38d7b4845281..0773bfa0342d 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -52,6 +52,7 @@ static void ip6_datagram_flow_key_init(struct flowi6 *fl6,
fl6->flowi6_mark = sk->sk_mark;
fl6->fl6_dport = inet->inet_dport;
fl6->fl6_sport = inet->inet_sport;
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
fl6->flowlabel = ip6_make_flowinfo(np->tclass, np->flow_label);
fl6->flowi6_uid = sk_uid(sk);
diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connection_sock.c
index 3e4ce8cb478e..f295f3efe243 100644
--- a/net/ipv6/inet6_connection_sock.c
+++ b/net/ipv6/inet6_connection_sock.c
@@ -45,6 +45,7 @@ struct dst_entry *inet6_csk_route_req(const struct sock *sk,
fl6->flowi6_mark = ireq->ir_mark;
fl6->fl6_dport = ireq->ir_rmt_port;
fl6->fl6_sport = htons(ireq->ir_num);
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
fl6->flowi6_uid = sk_uid(sk);
security_req_classify_flow(req, flowi6_to_flowi_common(fl6));
@@ -71,6 +72,7 @@ struct dst_entry *inet6_csk_route_socket(struct sock *sk,
fl6->daddr = sk->sk_v6_daddr;
fl6->saddr = np->saddr;
fl6->flowlabel = np->flow_label;
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
IP6_ECN_flow_xmit(sk, fl6->flowlabel);
if (sk->sk_protocol == IPPROTO_TCP)
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 8fc4766c8da9..a83ca449e308 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1191,6 +1191,34 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
if (err)
goto out_err_release;
+ /* Routing packets with a src addr not present in the host is not
+ * allowed unless the ANYSRC flag is set (i.e. with transparent socket).
+ * The address must be in the same L3 domain as the destination device.
+ */
+ if (!ipv6_addr_any(&fl6->saddr) &&
+ !(fl6->flowi6_flags & FLOWI_FLAG_ANYSRC)) {
+ struct net_device *oif_dev;
+
+ rcu_read_lock();
+ /* For local routes (*dst)->dev can be 'lo', which has no l3mdev
+ * master, so that the L3 domain wouldn't match if the source
+ * address is in a VRF-enslaved device. To avoid that, check the
+ * outgoing interface from the flowi6 structure instead.
+ */
+ if (fl6->flowi6_oif)
+ oif_dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
+ else
+ oif_dev = (*dst)->dev;
+
+ if (!ipv6_chk_addr_and_flags(net, &fl6->saddr, oif_dev,
+ 1, 1, IFA_F_TENTATIVE))
+ err = -ENETUNREACH;
+ rcu_read_unlock();
+
+ if (err)
+ goto out_err_release;
+ }
+
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
/*
* Here if the dst entry we've looked up
diff --git a/net/ipv6/netfilter.c b/net/ipv6/netfilter.c
index a7025ec87035..716fc6e246de 100644
--- a/net/ipv6/netfilter.c
+++ b/net/ipv6/netfilter.c
@@ -29,8 +29,9 @@ int ip6_route_me_harder(struct net *net, struct sock *sk_partial, struct sk_buff
struct flow_keys flkeys;
unsigned int hh_len;
struct dst_entry *dst;
- int strict = (ipv6_addr_type(&iph->daddr) &
- (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL));
+ int saddr_type = ipv6_addr_type(&iph->saddr);
+ int daddr_type = ipv6_addr_type(&iph->daddr);
+ int strict = daddr_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL);
struct flowi6 fl6 = {
.flowi6_l3mdev = l3mdev_master_ifindex(dev),
.flowi6_mark = skb->mark,
@@ -41,6 +42,12 @@ int ip6_route_me_harder(struct net *net, struct sock *sk_partial, struct sk_buff
};
int err;
+ fl6.flowi6_flags = sk ? inet_sk_flowi_flags(sk) : 0;
+ if (saddr_type & (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL))
+ fl6.flowi6_flags |= FLOWI_FLAG_ANYSRC;
+ else
+ fl6.saddr = in6addr_any;
+
if (sk && sk->sk_bound_dev_if)
fl6.flowi6_oif = sk->sk_bound_dev_if;
else if (strict)
diff --git a/net/ipv6/ping.c b/net/ipv6/ping.c
index 6e90d0bf9f3d..d0964396c2c5 100644
--- a/net/ipv6/ping.c
+++ b/net/ipv6/ping.c
@@ -140,6 +140,7 @@ static int ping_v6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
fl6.flowi6_proto = IPPROTO_ICMPV6;
fl6.saddr = np->saddr;
fl6.daddr = *daddr;
+ fl6.flowi6_flags = inet_sk_flowi_flags(sk);
fl6.flowi6_mark = ipc6.sockc.mark;
fl6.flowi6_uid = sk_uid(sk);
fl6.fl6_icmp_type = user_icmph.icmp6_type;
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index b965258cf9e5..ef5ddbe7ac10 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -887,6 +887,7 @@ static int rawv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
fl6.flowi6_oif = READ_ONCE(np->ucast_oif);
security_sk_classify_flow(sk, flowi6_to_flowi_common(&fl6));
+ fl6.flowi6_flags = inet_sk_flowi_flags(sk);
if (hdrincl)
fl6.flowi6_flags |= FLOWI_FLAG_KNOWN_NH;
diff --git a/net/ipv6/syncookies.c b/net/ipv6/syncookies.c
index b581cb1ee2e8..3d665e93fcac 100644
--- a/net/ipv6/syncookies.c
+++ b/net/ipv6/syncookies.c
@@ -242,6 +242,7 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb)
fl6.flowi6_mark = ireq->ir_mark;
fl6.fl6_dport = ireq->ir_rmt_port;
fl6.fl6_sport = inet_sk(sk)->inet_sport;
+ fl6.flowi6_flags = inet_sk_flowi_flags(sk);
fl6.flowi6_uid = sk_uid(sk);
security_req_classify_flow(req, flowi6_to_flowi_common(&fl6));
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index df9c29eb5c1f..ae69e71b0859 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -268,8 +268,9 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
fl6->flowi6_mark = sk->sk_mark;
fl6->fl6_dport = usin->sin6_port;
fl6->fl6_sport = inet->inet_sport;
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
if (IS_ENABLED(CONFIG_IP_ROUTE_MULTIPATH) && !fl6->fl6_sport)
- fl6->flowi6_flags = FLOWI_FLAG_ANY_SPORT;
+ fl6->flowi6_flags |= FLOWI_FLAG_ANY_SPORT;
fl6->flowi6_uid = sk_uid(sk);
opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk));
@@ -979,6 +980,7 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32
fl6.fl6_dport = t1->dest;
fl6.fl6_sport = t1->source;
fl6.flowi6_uid = sock_net_uid(net, sk && sk_fullsock(sk) ? sk : NULL);
+ fl6.flowi6_flags = sk && sk_fullsock(sk) ? inet_sk_flowi_flags(sk) : 0;
security_skb_classify_flow(skb, flowi6_to_flowi_common(&fl6));
/* Pass a socket to ip6_dst_lookup either it is for RST
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 93478d1ad576..5154d445ba9c 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1656,6 +1656,7 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
if (ipv6_addr_any(&fl6->saddr) && !ipv6_addr_any(&np->saddr))
fl6->saddr = np->saddr;
fl6->fl6_sport = inet->inet_sport;
+ fl6->flowi6_flags = inet_sk_flowi_flags(sk);
if (cgroup_bpf_enabled(CGROUP_UDP6_SENDMSG) && !connected) {
err = BPF_CGROUP_RUN_PROG_UDP6_SENDMSG_LOCK(sk,
diff --git a/net/l2tp/l2tp_ip6.c b/net/l2tp/l2tp_ip6.c
index bdaae1b64d25..22ae5bc2669a 100644
--- a/net/l2tp/l2tp_ip6.c
+++ b/net/l2tp/l2tp_ip6.c
@@ -633,6 +633,8 @@ static int l2tp_ip6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
else if (!fl6.flowi6_oif)
fl6.flowi6_oif = READ_ONCE(np->ucast_oif);
+ fl6.flowi6_flags = inet_sk_flowi_flags(sk);
+
security_sk_classify_flow(sk, flowi6_to_flowi_common(&fl6));
fl6.flowlabel = ip6_make_flowinfo(ipc6.tclass, fl6.flowlabel);
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address
2026-09-03 12:10 [PATCH net v2] net/ipv6: don't route packets with unknown source address Íñigo Huguet
@ 2026-09-03 14:56 ` Ido Schimmel
2026-09-03 22:38 ` Jakub Kicinski
2026-09-04 12:11 ` sashiko-bot
2 siblings, 0 replies; 5+ messages in thread
From: Ido Schimmel @ 2026-09-03 14:56 UTC (permalink / raw)
To: Íñigo Huguet
Cc: David Ahern, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Neal Cardwell, Pablo Neira Ayuso,
Florian Westphal, Willem de Bruijn, dcaratti, ihuguet,
Simon Horman, Kuniyuki Iwashima, Phil Sutter, Daniel Borkmann,
Junseo Lim, Martin KaFai Lau, Xuanqiang Luo,
Fernando Fernandez Mancera, Leon Hwang, Willem de Bruijn,
Kees Cook, Jeff Layton, Christian Brauner, Qi Tang, Joe Damato,
Breno Leitao, Li RongQing, open list:VRF, open list,
open list:NETFILTER, open list:NETFILTER,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_)
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@riseup.net>
>
> ---
>
> 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@riseup.net/
>
> 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@redhat.com/
https://lore.kernel.org/netdev/20260521135310.GC977@cmadams.net/
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.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address
2026-09-03 12:10 [PATCH net v2] net/ipv6: don't route packets with unknown source address Íñigo Huguet
2026-09-03 14:56 ` Ido Schimmel
@ 2026-09-03 22:38 ` Jakub Kicinski
2026-09-04 10:21 ` Matthieu Baerts
2026-09-04 12:11 ` sashiko-bot
2 siblings, 1 reply; 5+ messages in thread
From: Jakub Kicinski @ 2026-09-03 22:38 UTC (permalink / raw)
To: Íñigo Huguet
Cc: David Ahern, Andrew Lunn, David S. Miller, Eric Dumazet,
Paolo Abeni, Neal Cardwell, Ido Schimmel, Pablo Neira Ayuso,
Florian Westphal, Willem de Bruijn, dcaratti, ihuguet,
Simon Horman, Kuniyuki Iwashima, Phil Sutter, Daniel Borkmann,
Junseo Lim, Martin KaFai Lau, Xuanqiang Luo,
Fernando Fernandez Mancera, Leon Hwang, Willem de Bruijn,
Kees Cook, Jeff Layton, Christian Brauner, Qi Tang, Joe Damato,
Breno Leitao, Li RongQing, open list:VRF, open list,
open list:NETFILTER, open list:NETFILTER,
open list:BPF [MISC]:Keyword:(?:b|_)bpf(?:b|_)
On Thu, 3 Sep 2026 14:10:40 +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.
breaks a bunch of selftests, too, FWIW
Please make sure to mend those if you want to forge ahead as a non-fix
- selftests/net/xfrm_state.sh:
"IPv6 unreachable from IPsec gateway s2" [FAIL]
"IPv6 MTU exceeded from IPsec gateway s2" [FAIL]
(the equivalent IPv4 cases still pass)
- selftests/net/vrf_route_leaking.sh:
"VRF ICMP local IPv6" [FAIL]
"VRF tcp local connection IPv6" [FAIL]
"VRF udp local connection IPv6" [FAIL]
- selftests/net/srv6_encap_lookup_l3vpn_test.sh:
"IPv6 connectivity: rt-1 -> rt-2 (tenant 100/200)" [FAIL]
"IPv6 connectivity: rt-2 -> rt-1 (tenant 100/200)" [FAIL]
(again, the IPv4 variants of the same tests pass)
...
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address
2026-09-03 22:38 ` Jakub Kicinski
@ 2026-09-04 10:21 ` Matthieu Baerts
0 siblings, 0 replies; 5+ messages in thread
From: Matthieu Baerts @ 2026-09-04 10:21 UTC (permalink / raw)
To: Íñigo Huguet
Cc: Jakub Kicinski, David Ahern, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, Neal Cardwell, Ido Schimmel,
Pablo Neira Ayuso, Florian Westphal, Willem de Bruijn, dcaratti,
ihuguet, Simon Horman, Kuniyuki Iwashima, Phil Sutter,
Daniel Borkmann, Junseo Lim, Martin KaFai Lau, Xuanqiang Luo,
Fernando Fernandez Mancera, Leon Hwang, Willem de Bruijn,
Kees Cook, Jeff Layton, Christian Brauner, Qi Tang, Joe Damato,
Breno Leitao, Li RongQing, open list:VRF, open list,
open list:NETFILTER, open list:NETFILTER,
open list:BPF [MISC] :Keyword:(?:b|_)bpf(?:b|_)
Hi Íñigo,
On 04/09/2026 00:38, Jakub Kicinski wrote:
> On Thu, 3 Sep 2026 14:10:40 +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.
>
> breaks a bunch of selftests, too, FWIW
> Please make sure to mend those if you want to forge ahead as a non-fix
>
> - selftests/net/xfrm_state.sh:
> "IPv6 unreachable from IPsec gateway s2" [FAIL]
> "IPv6 MTU exceeded from IPsec gateway s2" [FAIL]
> (the equivalent IPv4 cases still pass)
>
> - selftests/net/vrf_route_leaking.sh:
> "VRF ICMP local IPv6" [FAIL]
> "VRF tcp local connection IPv6" [FAIL]
> "VRF udp local connection IPv6" [FAIL]
>
> - selftests/net/srv6_encap_lookup_l3vpn_test.sh:
> "IPv6 connectivity: rt-1 -> rt-2 (tenant 100/200)" [FAIL]
> "IPv6 connectivity: rt-2 -> rt-1 (tenant 100/200)" [FAIL]
> (again, the IPv4 variants of the same tests pass)
> ...
Just in case, do not hesitate to look at the new countainers that might
help you running those tests locally:
https://lore.kernel.org/9c66a2b4-2787-4421-bc9b-627de2f708bd@kernel.org
https://github.com/linux-netdev/nipa/wiki/Running-Netdev-CI-tests-locally
Cheers,
Matt
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net v2] net/ipv6: don't route packets with unknown source address
2026-09-03 12:10 [PATCH net v2] net/ipv6: don't route packets with unknown source address Íñigo Huguet
2026-09-03 14:56 ` Ido Schimmel
2026-09-03 22:38 ` Jakub Kicinski
@ 2026-09-04 12:11 ` sashiko-bot
2 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-04 12:11 UTC (permalink / raw)
To: Íñigo Huguet; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Link-Local Multicast addresses bypass source address clearing in `ip6_route_me_harder`.
- [High] TCP resets and ACKs from IPv6 transparent request (NEW_SYN_RECV) and TIME_WAIT sockets are dropped due to missing FLOWI_FLAG_ANYSRC.
- [High] SCTP IPv6 transparent sockets are broken because `FLOWI_FLAG_ANYSRC` is not set in `sctp_v6_get_dst`.
--
commit 0afe460974f748f250f2af7329befb04fd3c1e35
Author: Íñigo Huguet <ihuguet@riseup.net>
Subject: net/ipv6: don't route packets with unknown source address
This commit prevents outbound IPv6 packets from being routed if their source
address is not configured on the host, aligning the behavior with the IPv4
stack. It adds source address validation to ip6_dst_lookup_tail and applies
the FLOWI_FLAG_ANYSRC flag in various protocol handlers to allow certain
cases like transparent sockets.
> diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
> index 8fc4766c8da90..a83ca449e3089 100644
> --- a/net/ipv6/ip6_output.c
> +++ b/net/ipv6/ip6_output.c
> @@ -1191,6 +1191,34 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
> if (err)
> goto out_err_release;
>
> + /* Routing packets with a src addr not present in the host is not
> + * allowed unless the ANYSRC flag is set (i.e. with transparent socket).
> + * The address must be in the same L3 domain as the destination device.
> + */
> + if (!ipv6_addr_any(&fl6->saddr) &&
> + !(fl6->flowi6_flags & FLOWI_FLAG_ANYSRC)) {
> + struct net_device *oif_dev;
[Severity: High]
Does this new validation block break SCTP IPv6 transparent sockets?
In net/sctp/ipv6.c, sctp_v6_get_dst() calls ip6_dst_lookup_flow() but
was not updated in this patch to set fl6.flowi6_flags = inet_sk_flowi_flags(sk).
Since this new check blocks non-local source addresses unless FLOWI_FLAG_ANYSRC
is set, will a transparent SCTP socket attempting to route a packet with a
foreign source address now be incorrectly rejected here?
> +
> + rcu_read_lock();
> + /* For local routes (*dst)->dev can be 'lo', which has no l3mdev
> + * master, so that the L3 domain wouldn't match if the source
> + * address is in a VRF-enslaved device. To avoid that, check the
> + * outgoing interface from the flowi6 structure instead.
> + */
> + if (fl6->flowi6_oif)
> + oif_dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
> + else
> + oif_dev = (*dst)->dev;
> +
> + if (!ipv6_chk_addr_and_flags(net, &fl6->saddr, oif_dev,
> + 1, 1, IFA_F_TENTATIVE))
> + err = -ENETUNREACH;
> + rcu_read_unlock();
> +
> + if (err)
> + goto out_err_release;
> + }
> +
[ ... ]
> diff --git a/net/ipv6/netfilter.c b/net/ipv6/netfilter.c
> index a7025ec870359..716fc6e246de9 100644
> --- a/net/ipv6/netfilter.c
> +++ b/net/ipv6/netfilter.c
[ ... ]
> @@ -41,6 +42,12 @@ int ip6_route_me_harder(struct net *net, struct sock *sk_partial, struct sk_buff
> };
> int err;
>
> + fl6.flowi6_flags = sk ? inet_sk_flowi_flags(sk) : 0;
> + if (saddr_type & (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL))
> + fl6.flowi6_flags |= FLOWI_FLAG_ANYSRC;
> + else
> + fl6.saddr = in6addr_any;
[Severity: High]
Does this logic incorrectly evaluate to true for Link-Local Multicast
addresses?
If ipv6_addr_type() returns IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL
for an address like ff02::1, the bitmask check:
saddr_type & (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL)
will evaluate to true due to the IPV6_ADDR_LINKLOCAL bit being set.
Will this mistakenly apply FLOWI_FLAG_ANYSRC to link-local multicast
addresses, bypassing the in6addr_any clearing intended to sanitize
invalid source IPs?
> +
> if (sk && sk->sk_bound_dev_if)
> fl6.flowi6_oif = sk->sk_bound_dev_if;
> else if (strict)
[ ... ]
> diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
> index df9c29eb5c1f4..ae69e71b08598 100644
> --- a/net/ipv6/tcp_ipv6.c
> +++ b/net/ipv6/tcp_ipv6.c
[ ... ]
> @@ -979,6 +980,7 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32
> fl6.fl6_dport = t1->dest;
> fl6.fl6_sport = t1->source;
> fl6.flowi6_uid = sock_net_uid(net, sk && sk_fullsock(sk) ? sk : NULL);
> + fl6.flowi6_flags = sk && sk_fullsock(sk) ? inet_sk_flowi_flags(sk) : 0;
[Severity: High]
Can this inadvertently drop TCP resets and ACKs from IPv6 transparent
sockets in TIME_WAIT or NEW_SYN_RECV states?
When tcp_v6_send_response() is called for these states, sk_fullsock(sk)
evaluates to false. As a result, FLOWI_FLAG_ANYSRC is not applied.
Later, when ip6_dst_lookup_tail() validates the transparent (foreign)
source address without the ANYSRC flag, won't it reject the packet and
return -ENETUNREACH, breaking connection teardown and SYN-ACK responses?
> security_skb_classify_flow(skb, flowi6_to_flowi_common(&fl6));
>
> /* Pass a socket to ip6_dst_lookup either it is for RST
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260903121057.80006-1-ihuguet@riseup.net?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-04 12:11 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 12:10 [PATCH net v2] net/ipv6: don't route packets with unknown source address Íñigo Huguet
2026-09-03 14:56 ` Ido Schimmel
2026-09-03 22:38 ` Jakub Kicinski
2026-09-04 10:21 ` Matthieu Baerts
2026-09-04 12:11 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox