* [PATCH net-next 2/3] ipv6: add ip6_dst_lookup_flow_cookie
2026-11-04 15:53 [PATCH net-next 0/3] ipv6: tie the dst cookie to the lookup that returned the dst Ralf Lici
2026-11-04 15:53 ` [PATCH net-next 1/3] ipv6: stamp the default route node with a fresh serial on link failure Ralf Lici
@ 2026-11-04 15:53 ` Ralf Lici
2026-11-04 15:53 ` [PATCH net-next 3/3] ipv6: use the lookup cookie when caching the dst in sockets Ralf Lici
2 siblings, 0 replies; 4+ messages in thread
From: Ralf Lici @ 2026-11-04 15:53 UTC (permalink / raw)
To: netdev
Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Neal Cardwell,
Kuniyuki Iwashima, Steffen Klassert, Herbert Xu, Florian Westphal,
Sabrina Dubroca
Callers that keep a dst in a persistent cache read its validation cookie
with rt6_get_cookie once the lookup has returned. For a route without
its own sernum that is the current fn_sernum of the node, so a FIB
change landing in between, or an xfrm policy insertion, which restamps
every node, pairs the old dst with the new serial. dst_check then keeps
accepting it until the next change.
Add ip6_dst_lookup_flow_cookie, which returns the cookie together with
the dst. The cookie is taken between two reads of the IPv6 generation:
if it moved during the lookup the cookie is 0, which is never a node
serial, so a route dst can still be used for the packet at hand but is
not accepted again. An xfrm bundle ignores the cookie and is validated
against its own route and path cookies, which this patch does not
change.
dsts of nexthop objects are validated against the global generation and
ignore the cookie. For them the helper returns the generation read
before the lookup, and ip6_dst_check now requires it to match
rt->sernum, so a dst allocated after a FIB change is rejected for a
lookup that began before it. Callers that read the cookie from the dst
keep passing rt->sernum and are not affected.
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
include/net/ipv6.h | 13 ++++++++++++
net/ipv6/ip6_output.c | 49 +++++++++++++++++++++++++++++++++++++++++++
net/ipv6/route.c | 2 +-
3 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 3de07e738538..aca14bd5f78b 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -1062,6 +1062,11 @@ int ip6_dst_lookup(struct net *net, struct sock *sk, struct dst_entry **dst,
#if IS_ENABLED(CONFIG_IPV6)
struct dst_entry *ip6_dst_lookup_flow(struct net *net, const struct sock *sk, struct flowi6 *fl6,
const struct in6_addr *final_dst);
+struct dst_entry *ip6_dst_lookup_flow_cookie(struct net *net,
+ const struct sock *sk,
+ struct flowi6 *fl6,
+ const struct in6_addr *final_dst,
+ u32 *cookie);
#else
static inline struct dst_entry *ip6_dst_lookup_flow(struct net *net, const struct sock *sk,
struct flowi6 *fl6,
@@ -1069,6 +1074,14 @@ static inline struct dst_entry *ip6_dst_lookup_flow(struct net *net, const struc
{
return ERR_PTR(-EAFNOSUPPORT);
}
+
+static inline struct dst_entry *
+ip6_dst_lookup_flow_cookie(struct net *net, const struct sock *sk,
+ struct flowi6 *fl6, const struct in6_addr *final_dst,
+ u32 *cookie)
+{
+ return ERR_PTR(-EAFNOSUPPORT);
+}
#endif
struct dst_entry *ip6_sk_dst_lookup_flow(struct sock *sk, struct flowi6 *fl6,
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 25fe0ba98b1a..a2bee762e825 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1300,6 +1300,55 @@ struct dst_entry *ip6_dst_lookup_flow(struct net *net, const struct sock *sk, st
}
EXPORT_SYMBOL_GPL(ip6_dst_lookup_flow);
+/**
+ * ip6_dst_lookup_flow_cookie - perform route lookup on flow with ipsec
+ * and return the matching dst cookie
+ * @net: Network namespace to perform lookup in
+ * @sk: socket which provides route info
+ * @fl6: flow to lookup
+ * @final_dst: final destination address for ipsec lookup
+ * @cookie: where to store the cookie for the returned dst
+ *
+ * Same as ip6_dst_lookup_flow(), but also returns the cookie describing
+ * the routing state the dst was obtained from. A caller caching the dst
+ * should use this cookie when validating it with dst_check(). If the
+ * IPv6 FIB changed during the lookup, a route dst no longer validates
+ * with it. An xfrm bundle is validated by xfrm and ignores the cookie.
+ *
+ * It returns a valid dst pointer on success, or a pointer encoded
+ * error code.
+ */
+struct dst_entry *ip6_dst_lookup_flow_cookie(struct net *net,
+ const struct sock *sk,
+ struct flowi6 *fl6,
+ const struct in6_addr *final_dst,
+ u32 *cookie)
+{
+ const struct rt6_info *rt;
+ struct dst_entry *dst;
+ int gen0, gen1;
+
+ gen0 = rt_genid_ipv6(net);
+ /* read the generation before looking at the FIB or xfrm policies */
+ smp_rmb();
+
+ dst = ip6_dst_lookup_flow(net, sk, fl6, final_dst);
+ if (IS_ERR(dst))
+ return dst;
+
+ rt = dst_rt6_info(dst);
+ *cookie = rt->sernum ? gen0 : rt6_get_cookie(rt);
+
+ /* keep the FIB and cookie reads before the final generation read */
+ smp_rmb();
+ gen1 = rt_genid_ipv6(net);
+ if (unlikely(gen0 != gen1))
+ *cookie = 0;
+
+ return dst;
+}
+EXPORT_SYMBOL_GPL(ip6_dst_lookup_flow_cookie);
+
/**
* ip6_sk_dst_lookup_flow - perform socket cached route lookup on flow
* @sk: socket which provides the dst cache and route info
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index fa2b9033be6a..b04ba79ce3ea 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -2797,7 +2797,7 @@ INDIRECT_CALLABLE_SCOPE struct dst_entry *ip6_dst_check(struct dst_entry *dst,
rt = dst_rt6_info(dst);
if (rt->sernum)
- return rt6_is_valid(rt) ? dst : NULL;
+ return (rt6_is_valid(rt) && cookie == rt->sernum) ? dst : NULL;
rcu_read_lock();
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH net-next 3/3] ipv6: use the lookup cookie when caching the dst in sockets
2026-11-04 15:53 [PATCH net-next 0/3] ipv6: tie the dst cookie to the lookup that returned the dst Ralf Lici
2026-11-04 15:53 ` [PATCH net-next 1/3] ipv6: stamp the default route node with a fresh serial on link failure Ralf Lici
2026-11-04 15:53 ` [PATCH net-next 2/3] ipv6: add ip6_dst_lookup_flow_cookie Ralf Lici
@ 2026-11-04 15:53 ` Ralf Lici
2 siblings, 0 replies; 4+ messages in thread
From: Ralf Lici @ 2026-11-04 15:53 UTC (permalink / raw)
To: netdev
Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Neal Cardwell,
Kuniyuki Iwashima, Steffen Klassert, Herbert Xu, Florian Westphal,
Sabrina Dubroca
tcp_v6_connect, ip6_datagram_dst_update and ip6_sk_dst_lookup_flow store
the dst in the socket and read its cookie afterwards. If an xfrm policy
matching the flow is added after the route and policy lookup but before
the cookie is read, the plain route found without the policy is cached
with a post-insertion serial. Later packets pass the validation in
__sk_dst_check and skip xfrm, so a socket that should be using IPsec
keeps sending plaintext until the FIB changes again. It affects TCP and
connected UDP sockets alike.
Store the cookie returned by ip6_dst_lookup_flow_cookie instead. Add
ip6_dst_store_cookie for this and keep ip6_dst_store for callers that
have no lookup cookie.
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
include/net/ip6_route.h | 18 +++++++++++++-----
net/ipv6/datagram.c | 6 ++++--
net/ipv6/ip6_output.c | 6 ++++--
net/ipv6/route.c | 12 ++++++------
net/ipv6/tcp_ipv6.c | 5 +++--
5 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 95f936be32c6..2684f6069a5e 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -252,13 +252,13 @@ static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
/*
* Store a destination cache entry in a socket
*/
-static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst,
- bool daddr_set,
- bool saddr_set)
+static inline void ip6_dst_store_cookie(struct sock *sk, struct dst_entry *dst,
+ u32 cookie, bool daddr_set,
+ bool saddr_set)
{
struct ipv6_pinfo *np = inet6_sk(sk);
- np->dst_cookie = rt6_get_cookie(dst_rt6_info(dst));
+ np->dst_cookie = cookie;
sk_setup_caps(sk, dst);
np->daddr_cache = daddr_set;
#ifdef CONFIG_IPV6_SUBTREES
@@ -266,8 +266,16 @@ static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst,
#endif
}
+static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst,
+ bool daddr_set,
+ bool saddr_set)
+{
+ ip6_dst_store_cookie(sk, dst, rt6_get_cookie(dst_rt6_info(dst)),
+ daddr_set, saddr_set);
+}
+
void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
- const struct flowi6 *fl6);
+ const struct flowi6 *fl6, u32 cookie);
static inline bool ipv6_unicast_destination(const struct sk_buff *skb)
{
diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c
index 191c9733ff9f..5797bf3e1333 100644
--- a/net/ipv6/datagram.c
+++ b/net/ipv6/datagram.c
@@ -78,6 +78,7 @@ int ip6_datagram_dst_update(struct sock *sk, bool fix_sk_saddr)
struct in6_addr *final_p;
struct dst_entry *dst;
struct flowi6 *fl6;
+ u32 cookie;
int err = 0;
if (inet6_test_bit(SNDFLOW, sk) &&
@@ -94,7 +95,8 @@ int ip6_datagram_dst_update(struct sock *sk, bool fix_sk_saddr)
final_p = fl6_update_dst(fl6, opt, &np->final);
rcu_read_unlock();
- dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);
+ dst = ip6_dst_lookup_flow_cookie(sock_net(sk), sk, fl6, final_p,
+ &cookie);
if (IS_ERR(dst)) {
err = PTR_ERR(dst);
goto out;
@@ -112,7 +114,7 @@ int ip6_datagram_dst_update(struct sock *sk, bool fix_sk_saddr)
}
}
- ip6_sk_dst_store_flow(sk, dst, fl6);
+ ip6_sk_dst_store_flow(sk, dst, fl6, cookie);
out:
fl6_sock_release(flowlabel);
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index a2bee762e825..0e5089f2a7a7 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -1372,14 +1372,16 @@ struct dst_entry *ip6_sk_dst_lookup_flow(struct sock *sk, struct flowi6 *fl6,
bool connected)
{
struct dst_entry *dst = sk_dst_check(sk, inet6_sk(sk)->dst_cookie);
+ u32 cookie;
dst = ip6_sk_dst_check(sk, dst, fl6);
if (dst)
return dst;
- dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_dst);
+ dst = ip6_dst_lookup_flow_cookie(sock_net(sk), sk, fl6, final_dst,
+ &cookie);
if (connected && !IS_ERR(dst))
- ip6_sk_dst_store_flow(sk, dst_clone(dst), fl6);
+ ip6_sk_dst_store_flow(sk, dst_clone(dst), fl6, cookie);
return dst;
}
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index b04ba79ce3ea..4107e54c7a1c 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3028,19 +3028,19 @@ void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu)
}
void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
- const struct flowi6 *fl6)
+ const struct flowi6 *fl6, u32 cookie)
{
#ifdef CONFIG_IPV6_SUBTREES
struct ipv6_pinfo *np = inet6_sk(sk);
#endif
- ip6_dst_store(sk, dst,
- ipv6_addr_equal(&fl6->daddr, &sk->sk_v6_daddr),
+ ip6_dst_store_cookie(sk, dst, cookie,
+ ipv6_addr_equal(&fl6->daddr, &sk->sk_v6_daddr),
#ifdef CONFIG_IPV6_SUBTREES
- ipv6_addr_equal(&fl6->saddr, &np->saddr) ?
- true :
+ ipv6_addr_equal(&fl6->saddr, &np->saddr) ?
+ true :
#endif
- false);
+ false);
}
static bool ip6_redirect_nh_match(const struct fib6_result *res,
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index e006a30360ea..e45001602446 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -145,6 +145,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
struct dst_entry *dst;
struct flowi6 *fl6;
int addr_type;
+ u32 cookie;
int err;
if (addr_len < SIN6_LEN_RFC2133)
@@ -288,7 +289,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
*/
ip6_ecmp_set_mp_hash(net, fl6, sk->sk_txhash);
- dst = ip6_dst_lookup_flow(net, sk, fl6, final_p);
+ dst = ip6_dst_lookup_flow_cookie(net, sk, fl6, final_p, &cookie);
if (IS_ERR(dst)) {
err = PTR_ERR(dst);
goto failure;
@@ -312,7 +313,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
inet->inet_rcv_saddr = LOOPBACK4_IPV6;
sk->sk_gso_type = SKB_GSO_TCPV6;
- ip6_dst_store(sk, dst, false, false);
+ ip6_dst_store_cookie(sk, dst, cookie, false, false);
icsk->icsk_ext_hdr_len = 0;
if (opt)
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread