* [PATCH net v2 0/2] seg6: fix dst_cache sharing in seg6 lwtunnel @ 2026-04-01 18:57 Andrea Mayer 2026-04-01 18:57 ` [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths " Andrea Mayer 2026-04-01 18:57 ` [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation " Andrea Mayer 0 siblings, 2 replies; 6+ messages in thread From: Andrea Mayer @ 2026-04-01 18:57 UTC (permalink / raw) To: netdev Cc: davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, Andrea Mayer The seg6 lwtunnel encap uses a single per-route dst_cache shared between seg6_input_core() and seg6_output_core(). These two paths can perform the post-encap SID lookup in different routing contexts (e.g., ip rules matching on the ingress interface, or VRF table separation). Whichever path runs first populates the cache, and the other reuses it blindly, bypassing its own lookup. Patch 1 fixes this by splitting the cache into cache_input and cache_output. Patch 2 adds a selftest that validates the isolation. Changes v1 -> v2: - Patch 2: fix SKIP message wording (Nicolas) Andrea Mayer (2): seg6: separate dst_cache for input and output paths in seg6 lwtunnel selftests: seg6: add test for dst_cache isolation in seg6 lwtunnel net/ipv6/seg6_iptunnel.c | 34 ++-- tools/testing/selftests/net/Makefile | 1 + .../selftests/net/srv6_iptunnel_cache.sh | 177 ++++++++++++++++++ 3 files changed, 201 insertions(+), 11 deletions(-) create mode 100755 tools/testing/selftests/net/srv6_iptunnel_cache.sh -- 2.43.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths in seg6 lwtunnel 2026-04-01 18:57 [PATCH net v2 0/2] seg6: fix dst_cache sharing in seg6 lwtunnel Andrea Mayer @ 2026-04-01 18:57 ` Andrea Mayer 2026-04-02 18:30 ` Justin Iurman 2026-04-01 18:57 ` [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation " Andrea Mayer 1 sibling, 1 reply; 6+ messages in thread From: Andrea Mayer @ 2026-04-01 18:57 UTC (permalink / raw) To: netdev Cc: davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, Andrea Mayer, stable The seg6 lwtunnel uses a single dst_cache per encap route, shared between seg6_input_core() and seg6_output_core(). These two paths can perform the post-encap SID lookup in different routing contexts (e.g., ip rules matching on the ingress interface, or VRF table separation). Whichever path runs first populates the cache, and the other reuses it blindly, bypassing its own lookup. Fix this by splitting the cache into cache_input and cache_output, so each path maintains its own cached dst independently. Fixes: 6c8702c60b88 ("ipv6: sr: add support for SRH encapsulation and injection with lwtunnels") Cc: stable@vger.kernel.org Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it> Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> --- net/ipv6/seg6_iptunnel.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 3e1b9991131a..d6a0f7df9080 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -48,7 +48,8 @@ static size_t seg6_lwt_headroom(struct seg6_iptunnel_encap *tuninfo) } struct seg6_lwt { - struct dst_cache cache; + struct dst_cache cache_input; + struct dst_cache cache_output; struct seg6_iptunnel_encap tuninfo[]; }; @@ -488,7 +489,7 @@ static int seg6_input_core(struct net *net, struct sock *sk, slwt = seg6_lwt_lwtunnel(lwtst); local_bh_disable(); - dst = dst_cache_get(&slwt->cache); + dst = dst_cache_get(&slwt->cache_input); local_bh_enable(); err = seg6_do_srh(skb, dst); @@ -504,7 +505,7 @@ static int seg6_input_core(struct net *net, struct sock *sk, /* cache only if we don't create a dst reference loop */ if (!dst->error && lwtst != dst->lwtstate) { local_bh_disable(); - dst_cache_set_ip6(&slwt->cache, dst, + dst_cache_set_ip6(&slwt->cache_input, dst, &ipv6_hdr(skb)->saddr); local_bh_enable(); } @@ -564,7 +565,7 @@ static int seg6_output_core(struct net *net, struct sock *sk, slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate); local_bh_disable(); - dst = dst_cache_get(&slwt->cache); + dst = dst_cache_get(&slwt->cache_output); local_bh_enable(); err = seg6_do_srh(skb, dst); @@ -591,7 +592,7 @@ static int seg6_output_core(struct net *net, struct sock *sk, /* cache only if we don't create a dst reference loop */ if (orig_dst->lwtstate != dst->lwtstate) { local_bh_disable(); - dst_cache_set_ip6(&slwt->cache, dst, &fl6.saddr); + dst_cache_set_ip6(&slwt->cache_output, dst, &fl6.saddr); local_bh_enable(); } @@ -701,11 +702,13 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, slwt = seg6_lwt_lwtunnel(newts); - err = dst_cache_init(&slwt->cache, GFP_ATOMIC); - if (err) { - kfree(newts); - return err; - } + err = dst_cache_init(&slwt->cache_input, GFP_ATOMIC); + if (err) + goto err_free_newts; + + err = dst_cache_init(&slwt->cache_output, GFP_ATOMIC); + if (err) + goto err_destroy_input; memcpy(&slwt->tuninfo, tuninfo, tuninfo_len); @@ -720,11 +723,20 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, *ts = newts; return 0; + +err_destroy_input: + dst_cache_destroy(&slwt->cache_input); +err_free_newts: + kfree(newts); + return err; } static void seg6_destroy_state(struct lwtunnel_state *lwt) { - dst_cache_destroy(&seg6_lwt_lwtunnel(lwt)->cache); + struct seg6_lwt *slwt = seg6_lwt_lwtunnel(lwt); + + dst_cache_destroy(&slwt->cache_input); + dst_cache_destroy(&slwt->cache_output); } static int seg6_fill_encap_info(struct sk_buff *skb, -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths in seg6 lwtunnel 2026-04-01 18:57 ` [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths " Andrea Mayer @ 2026-04-02 18:30 ` Justin Iurman 0 siblings, 0 replies; 6+ messages in thread From: Justin Iurman @ 2026-04-02 18:30 UTC (permalink / raw) To: Andrea Mayer, netdev Cc: davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, stable On 4/1/26 20:57, Andrea Mayer wrote: > The seg6 lwtunnel uses a single dst_cache per encap route, shared > between seg6_input_core() and seg6_output_core(). These two paths > can perform the post-encap SID lookup in different routing contexts > (e.g., ip rules matching on the ingress interface, or VRF table > separation). Whichever path runs first populates the cache, and the > other reuses it blindly, bypassing its own lookup. > > Fix this by splitting the cache into cache_input and cache_output, > so each path maintains its own cached dst independently. > > Fixes: 6c8702c60b88 ("ipv6: sr: add support for SRH encapsulation and injection with lwtunnels") > Cc: stable@vger.kernel.org > Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it> > Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> > --- > net/ipv6/seg6_iptunnel.c | 34 +++++++++++++++++++++++----------- > 1 file changed, 23 insertions(+), 11 deletions(-) > > diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c > index 3e1b9991131a..d6a0f7df9080 100644 > --- a/net/ipv6/seg6_iptunnel.c > +++ b/net/ipv6/seg6_iptunnel.c > @@ -48,7 +48,8 @@ static size_t seg6_lwt_headroom(struct seg6_iptunnel_encap *tuninfo) > } > > struct seg6_lwt { > - struct dst_cache cache; > + struct dst_cache cache_input; > + struct dst_cache cache_output; > struct seg6_iptunnel_encap tuninfo[]; > }; > > @@ -488,7 +489,7 @@ static int seg6_input_core(struct net *net, struct sock *sk, > slwt = seg6_lwt_lwtunnel(lwtst); > > local_bh_disable(); > - dst = dst_cache_get(&slwt->cache); > + dst = dst_cache_get(&slwt->cache_input); > local_bh_enable(); > > err = seg6_do_srh(skb, dst); > @@ -504,7 +505,7 @@ static int seg6_input_core(struct net *net, struct sock *sk, > /* cache only if we don't create a dst reference loop */ > if (!dst->error && lwtst != dst->lwtstate) { > local_bh_disable(); > - dst_cache_set_ip6(&slwt->cache, dst, > + dst_cache_set_ip6(&slwt->cache_input, dst, > &ipv6_hdr(skb)->saddr); > local_bh_enable(); > } > @@ -564,7 +565,7 @@ static int seg6_output_core(struct net *net, struct sock *sk, > slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate); > > local_bh_disable(); > - dst = dst_cache_get(&slwt->cache); > + dst = dst_cache_get(&slwt->cache_output); > local_bh_enable(); > > err = seg6_do_srh(skb, dst); > @@ -591,7 +592,7 @@ static int seg6_output_core(struct net *net, struct sock *sk, > /* cache only if we don't create a dst reference loop */ > if (orig_dst->lwtstate != dst->lwtstate) { > local_bh_disable(); > - dst_cache_set_ip6(&slwt->cache, dst, &fl6.saddr); > + dst_cache_set_ip6(&slwt->cache_output, dst, &fl6.saddr); > local_bh_enable(); > } > > @@ -701,11 +702,13 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, > > slwt = seg6_lwt_lwtunnel(newts); > > - err = dst_cache_init(&slwt->cache, GFP_ATOMIC); > - if (err) { > - kfree(newts); > - return err; > - } > + err = dst_cache_init(&slwt->cache_input, GFP_ATOMIC); > + if (err) > + goto err_free_newts; > + > + err = dst_cache_init(&slwt->cache_output, GFP_ATOMIC); > + if (err) > + goto err_destroy_input; > > memcpy(&slwt->tuninfo, tuninfo, tuninfo_len); > > @@ -720,11 +723,20 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, > *ts = newts; > > return 0; > + > +err_destroy_input: > + dst_cache_destroy(&slwt->cache_input); > +err_free_newts: > + kfree(newts); > + return err; > } > > static void seg6_destroy_state(struct lwtunnel_state *lwt) > { > - dst_cache_destroy(&seg6_lwt_lwtunnel(lwt)->cache); > + struct seg6_lwt *slwt = seg6_lwt_lwtunnel(lwt); > + > + dst_cache_destroy(&slwt->cache_input); > + dst_cache_destroy(&slwt->cache_output); > } > > static int seg6_fill_encap_info(struct sk_buff *skb, Reviewed-by: Justin Iurman <justin.iurman@gmail.com> ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation in seg6 lwtunnel 2026-04-01 18:57 [PATCH net v2 0/2] seg6: fix dst_cache sharing in seg6 lwtunnel Andrea Mayer 2026-04-01 18:57 ` [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths " Andrea Mayer @ 2026-04-01 18:57 ` Andrea Mayer 2026-04-02 18:35 ` Justin Iurman 1 sibling, 1 reply; 6+ messages in thread From: Andrea Mayer @ 2026-04-01 18:57 UTC (permalink / raw) To: netdev Cc: davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, Andrea Mayer, Shuah Khan, linux-kselftest Add a selftest that verifies the dst_cache in seg6 lwtunnel is not shared between the input (forwarding) and output (locally generated) paths. The test creates three namespaces (ns_src, ns_router, ns_dst) connected in a line. An SRv6 encap route on ns_router encapsulates traffic destined to cafe::1 with SID fc00::100. The SID is reachable only for forwarded traffic (from ns_src) via an ip rule matching the ingress interface (iif veth-r0 lookup 100), and blackholed in the main table. The test verifies that: 1. A packet generated locally on ns_router does not reach ns_dst with an empty cache, since the SID is blackholed; 2. A forwarded packet from ns_src populates the input cache from table 100 and reaches ns_dst; 3. A packet generated locally on ns_router still does not reach ns_dst after the input cache is populated, confirming the output path does not reuse the input cache entry. Both the forwarded and local packets are pinned to the same CPU with taskset, since dst_cache is per-cpu. Cc: Shuah Khan <shuah@kernel.org> Cc: linux-kselftest@vger.kernel.org Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it> Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> --- Changes v1 -> v2: - fix SKIP message wording (Nicolas) --- tools/testing/selftests/net/Makefile | 1 + .../selftests/net/srv6_iptunnel_cache.sh | 177 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100755 tools/testing/selftests/net/srv6_iptunnel_cache.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 605c54c0e8a3..c709523c99c6 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -89,6 +89,7 @@ TEST_PROGS := \ srv6_end_x_next_csid_l3vpn_test.sh \ srv6_hencap_red_l3vpn_test.sh \ srv6_hl2encap_red_l2vpn_test.sh \ + srv6_iptunnel_cache.sh \ stress_reuseport_listen.sh \ tcp_fastopen_backup_key.sh \ test_bpf.sh \ diff --git a/tools/testing/selftests/net/srv6_iptunnel_cache.sh b/tools/testing/selftests/net/srv6_iptunnel_cache.sh new file mode 100755 index 000000000000..1f3ebb930eb2 --- /dev/null +++ b/tools/testing/selftests/net/srv6_iptunnel_cache.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# author: Andrea Mayer <andrea.mayer@uniroma2.it> + +# This test verifies that the seg6 lwtunnel does not share the dst_cache +# between the input (forwarding) and output (locally generated) paths. +# +# A shared dst_cache allows a forwarded packet to populate the cache and a +# subsequent locally generated packet to silently reuse that entry, bypassing +# its own route lookup. To expose this, the SID is made reachable only for +# forwarded traffic (via an ip rule matching iif) and blackholed for everything +# else. A local ping on ns_router must always hit the blackhole; +# if it succeeds after a forwarded packet has populated the +# cache, the bug is confirmed. +# +# Both forwarded and local packets are pinned to the same CPU with taskset, +# since dst_cache is per-cpu. +# +# +# +--------------------+ +--------------------+ +# | ns_src | | ns_dst | +# | | | | +# | veth-s0 | | veth-d0 | +# | fd00::1/64 | | fd01::2/64 | +# +-------|------------+ +----------|---------+ +# | | +# | +--------------------+ | +# | | ns_router | | +# | | | | +# +----------->+ veth-r0 veth-r1 +<-------------+ +# | fd00::2 fd01::1 | +# +--------------------+ +# +# +# ns_router: encap (main table) +# +---------+---------------------------------------+ +# | dst | action | +# +---------+---------------------------------------+ +# | cafe::1 | encap seg6 mode encap segs fc00::100 | +# +---------+---------------------------------------+ +# +# ns_router: post-encap SID resolution +# +-------+------------+----------------------------+ +# | table | dst | action | +# +-------+------------+----------------------------+ +# | 100 | fc00::100 | via fd01::2 dev veth-r1 | +# +-------+------------+----------------------------+ +# | main | fc00::100 | blackhole | +# +-------+------------+----------------------------+ +# +# ns_router: ip rule +# +------------------+------------------------------+ +# | match | action | +# +------------------+------------------------------+ +# | iif veth-r0 | lookup 100 | +# +------------------+------------------------------+ +# +# ns_dst: SRv6 decap (main table) +# +--------------+----------------------------------+ +# | SID | action | +# +--------------+----------------------------------+ +# | fc00::100 | End.DT6 table 255 (local) | +# +--------------+----------------------------------+ + +source lib.sh + +readonly SID="fc00::100" +readonly DEST="cafe::1" + +readonly SRC_MAC="02:00:00:00:00:01" +readonly RTR_R0_MAC="02:00:00:00:00:02" +readonly RTR_R1_MAC="02:00:00:00:00:03" +readonly DST_MAC="02:00:00:00:00:04" + +cleanup() +{ + cleanup_ns "${NS_SRC}" "${NS_RTR}" "${NS_DST}" +} + +check_prerequisites() +{ + if ! command -v taskset &>/dev/null; then + echo "SKIP: taskset not found" + exit "${ksft_skip}" + fi +} + +setup() +{ + setup_ns NS_SRC NS_RTR NS_DST + + ip link add veth-s0 netns "${NS_SRC}" type veth \ + peer name veth-r0 netns "${NS_RTR}" + ip link add veth-r1 netns "${NS_RTR}" type veth \ + peer name veth-d0 netns "${NS_DST}" + + ip -n "${NS_SRC}" link set veth-s0 address "${SRC_MAC}" + ip -n "${NS_RTR}" link set veth-r0 address "${RTR_R0_MAC}" + ip -n "${NS_RTR}" link set veth-r1 address "${RTR_R1_MAC}" + ip -n "${NS_DST}" link set veth-d0 address "${DST_MAC}" + + # ns_src + ip -n "${NS_SRC}" link set veth-s0 up + ip -n "${NS_SRC}" addr add fd00::1/64 dev veth-s0 nodad + ip -n "${NS_SRC}" -6 route add "${DEST}"/128 via fd00::2 + + # ns_router + ip -n "${NS_RTR}" link set veth-r0 up + ip -n "${NS_RTR}" addr add fd00::2/64 dev veth-r0 nodad + ip -n "${NS_RTR}" link set veth-r1 up + ip -n "${NS_RTR}" addr add fd01::1/64 dev veth-r1 nodad + ip netns exec "${NS_RTR}" sysctl -qw net.ipv6.conf.all.forwarding=1 + + ip -n "${NS_RTR}" -6 route add "${DEST}"/128 \ + encap seg6 mode encap segs "${SID}" dev veth-r0 + ip -n "${NS_RTR}" -6 route add "${SID}"/128 table 100 \ + via fd01::2 dev veth-r1 + ip -n "${NS_RTR}" -6 route add blackhole "${SID}"/128 + ip -n "${NS_RTR}" -6 rule add iif veth-r0 lookup 100 + + # ns_dst + ip -n "${NS_DST}" link set veth-d0 up + ip -n "${NS_DST}" addr add fd01::2/64 dev veth-d0 nodad + ip -n "${NS_DST}" addr add "${DEST}"/128 dev lo nodad + ip -n "${NS_DST}" -6 route add "${SID}"/128 \ + encap seg6local action End.DT6 table 255 dev veth-d0 + ip -n "${NS_DST}" -6 route add fd00::/64 via fd01::1 + + # static neighbors + ip -n "${NS_SRC}" -6 neigh add fd00::2 dev veth-s0 \ + lladdr "${RTR_R0_MAC}" nud permanent + ip -n "${NS_RTR}" -6 neigh add fd00::1 dev veth-r0 \ + lladdr "${SRC_MAC}" nud permanent + ip -n "${NS_RTR}" -6 neigh add fd01::2 dev veth-r1 \ + lladdr "${DST_MAC}" nud permanent + ip -n "${NS_DST}" -6 neigh add fd01::1 dev veth-d0 \ + lladdr "${RTR_R1_MAC}" nud permanent +} + +test_cache_isolation() +{ + RET=0 + + # local ping with empty cache: must fail (SID is blackholed) + if ip netns exec "${NS_RTR}" taskset -c 0 \ + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then + echo "SKIP: local ping succeeded, topology broken" + exit "${ksft_skip}" + fi + + # forward from ns_src to populate the input cache + if ! ip netns exec "${NS_SRC}" taskset -c 0 \ + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then + echo "SKIP: forwarded ping failed, topology broken" + exit "${ksft_skip}" + fi + + # local ping again: must still fail; if the output path reuses + # the input cache, it bypasses the blackhole and the ping succeeds + if ip netns exec "${NS_RTR}" taskset -c 0 \ + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then + echo "FAIL: output path used dst cached by input path" + RET="${ksft_fail}" + else + echo "PASS: output path dst_cache is independent" + fi + + return "${RET}" +} + +trap cleanup EXIT + +check_prerequisites +setup +test_cache_isolation +exit "${RET}" -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation in seg6 lwtunnel 2026-04-01 18:57 ` [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation " Andrea Mayer @ 2026-04-02 18:35 ` Justin Iurman 2026-04-03 14:46 ` Andrea Mayer 0 siblings, 1 reply; 6+ messages in thread From: Justin Iurman @ 2026-04-02 18:35 UTC (permalink / raw) To: Andrea Mayer, netdev Cc: davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, Shuah Khan, linux-kselftest On 4/1/26 20:57, Andrea Mayer wrote: > Add a selftest that verifies the dst_cache in seg6 lwtunnel is not > shared between the input (forwarding) and output (locally generated) > paths. > > The test creates three namespaces (ns_src, ns_router, ns_dst) > connected in a line. An SRv6 encap route on ns_router encapsulates > traffic destined to cafe::1 with SID fc00::100. The SID is > reachable only for forwarded traffic (from ns_src) via an ip rule > matching the ingress interface (iif veth-r0 lookup 100), and > blackholed in the main table. > > The test verifies that: > > 1. A packet generated locally on ns_router does not reach > ns_dst with an empty cache, since the SID is blackholed; > 2. A forwarded packet from ns_src populates the input cache > from table 100 and reaches ns_dst; > 3. A packet generated locally on ns_router still does not > reach ns_dst after the input cache is populated, > confirming the output path does not reuse the input > cache entry. > > Both the forwarded and local packets are pinned to the same CPU > with taskset, since dst_cache is per-cpu. > > Cc: Shuah Khan <shuah@kernel.org> > Cc: linux-kselftest@vger.kernel.org > Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it> > Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> > --- > Changes v1 -> v2: > - fix SKIP message wording (Nicolas) > --- > tools/testing/selftests/net/Makefile | 1 + > .../selftests/net/srv6_iptunnel_cache.sh | 177 ++++++++++++++++++ > 2 files changed, 178 insertions(+) > create mode 100755 tools/testing/selftests/net/srv6_iptunnel_cache.sh > > diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile > index 605c54c0e8a3..c709523c99c6 100644 > --- a/tools/testing/selftests/net/Makefile > +++ b/tools/testing/selftests/net/Makefile > @@ -89,6 +89,7 @@ TEST_PROGS := \ > srv6_end_x_next_csid_l3vpn_test.sh \ > srv6_hencap_red_l3vpn_test.sh \ > srv6_hl2encap_red_l2vpn_test.sh \ > + srv6_iptunnel_cache.sh \ > stress_reuseport_listen.sh \ > tcp_fastopen_backup_key.sh \ > test_bpf.sh \ > diff --git a/tools/testing/selftests/net/srv6_iptunnel_cache.sh b/tools/testing/selftests/net/srv6_iptunnel_cache.sh > new file mode 100755 > index 000000000000..1f3ebb930eb2 > --- /dev/null > +++ b/tools/testing/selftests/net/srv6_iptunnel_cache.sh > @@ -0,0 +1,177 @@ > +#!/bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# > +# author: Andrea Mayer <andrea.mayer@uniroma2.it> > + > +# This test verifies that the seg6 lwtunnel does not share the dst_cache > +# between the input (forwarding) and output (locally generated) paths. > +# > +# A shared dst_cache allows a forwarded packet to populate the cache and a > +# subsequent locally generated packet to silently reuse that entry, bypassing > +# its own route lookup. To expose this, the SID is made reachable only for > +# forwarded traffic (via an ip rule matching iif) and blackholed for everything > +# else. A local ping on ns_router must always hit the blackhole; > +# if it succeeds after a forwarded packet has populated the > +# cache, the bug is confirmed. > +# > +# Both forwarded and local packets are pinned to the same CPU with taskset, > +# since dst_cache is per-cpu. > +# > +# > +# +--------------------+ +--------------------+ > +# | ns_src | | ns_dst | > +# | | | | > +# | veth-s0 | | veth-d0 | > +# | fd00::1/64 | | fd01::2/64 | > +# +-------|------------+ +----------|---------+ > +# | | > +# | +--------------------+ | > +# | | ns_router | | > +# | | | | > +# +----------->+ veth-r0 veth-r1 +<-------------+ > +# | fd00::2 fd01::1 | > +# +--------------------+ > +# > +# > +# ns_router: encap (main table) > +# +---------+---------------------------------------+ > +# | dst | action | > +# +---------+---------------------------------------+ > +# | cafe::1 | encap seg6 mode encap segs fc00::100 | > +# +---------+---------------------------------------+ > +# > +# ns_router: post-encap SID resolution > +# +-------+------------+----------------------------+ > +# | table | dst | action | > +# +-------+------------+----------------------------+ > +# | 100 | fc00::100 | via fd01::2 dev veth-r1 | > +# +-------+------------+----------------------------+ > +# | main | fc00::100 | blackhole | > +# +-------+------------+----------------------------+ > +# > +# ns_router: ip rule > +# +------------------+------------------------------+ > +# | match | action | > +# +------------------+------------------------------+ > +# | iif veth-r0 | lookup 100 | > +# +------------------+------------------------------+ > +# > +# ns_dst: SRv6 decap (main table) > +# +--------------+----------------------------------+ > +# | SID | action | > +# +--------------+----------------------------------+ > +# | fc00::100 | End.DT6 table 255 (local) | > +# +--------------+----------------------------------+ > + > +source lib.sh > + > +readonly SID="fc00::100" > +readonly DEST="cafe::1" > + > +readonly SRC_MAC="02:00:00:00:00:01" > +readonly RTR_R0_MAC="02:00:00:00:00:02" > +readonly RTR_R1_MAC="02:00:00:00:00:03" > +readonly DST_MAC="02:00:00:00:00:04" > + > +cleanup() > +{ > + cleanup_ns "${NS_SRC}" "${NS_RTR}" "${NS_DST}" > +} > + > +check_prerequisites() > +{ > + if ! command -v taskset &>/dev/null; then > + echo "SKIP: taskset not found" > + exit "${ksft_skip}" > + fi > +} > + > +setup() > +{ > + setup_ns NS_SRC NS_RTR NS_DST > + > + ip link add veth-s0 netns "${NS_SRC}" type veth \ > + peer name veth-r0 netns "${NS_RTR}" > + ip link add veth-r1 netns "${NS_RTR}" type veth \ > + peer name veth-d0 netns "${NS_DST}" > + > + ip -n "${NS_SRC}" link set veth-s0 address "${SRC_MAC}" > + ip -n "${NS_RTR}" link set veth-r0 address "${RTR_R0_MAC}" > + ip -n "${NS_RTR}" link set veth-r1 address "${RTR_R1_MAC}" > + ip -n "${NS_DST}" link set veth-d0 address "${DST_MAC}" > + > + # ns_src > + ip -n "${NS_SRC}" link set veth-s0 up > + ip -n "${NS_SRC}" addr add fd00::1/64 dev veth-s0 nodad > + ip -n "${NS_SRC}" -6 route add "${DEST}"/128 via fd00::2 > + > + # ns_router > + ip -n "${NS_RTR}" link set veth-r0 up > + ip -n "${NS_RTR}" addr add fd00::2/64 dev veth-r0 nodad > + ip -n "${NS_RTR}" link set veth-r1 up > + ip -n "${NS_RTR}" addr add fd01::1/64 dev veth-r1 nodad > + ip netns exec "${NS_RTR}" sysctl -qw net.ipv6.conf.all.forwarding=1 > + > + ip -n "${NS_RTR}" -6 route add "${DEST}"/128 \ > + encap seg6 mode encap segs "${SID}" dev veth-r0 > + ip -n "${NS_RTR}" -6 route add "${SID}"/128 table 100 \ > + via fd01::2 dev veth-r1 > + ip -n "${NS_RTR}" -6 route add blackhole "${SID}"/128 > + ip -n "${NS_RTR}" -6 rule add iif veth-r0 lookup 100 > + > + # ns_dst > + ip -n "${NS_DST}" link set veth-d0 up > + ip -n "${NS_DST}" addr add fd01::2/64 dev veth-d0 nodad > + ip -n "${NS_DST}" addr add "${DEST}"/128 dev lo nodad > + ip -n "${NS_DST}" -6 route add "${SID}"/128 \ > + encap seg6local action End.DT6 table 255 dev veth-d0 > + ip -n "${NS_DST}" -6 route add fd00::/64 via fd01::1 > + > + # static neighbors > + ip -n "${NS_SRC}" -6 neigh add fd00::2 dev veth-s0 \ > + lladdr "${RTR_R0_MAC}" nud permanent > + ip -n "${NS_RTR}" -6 neigh add fd00::1 dev veth-r0 \ > + lladdr "${SRC_MAC}" nud permanent > + ip -n "${NS_RTR}" -6 neigh add fd01::2 dev veth-r1 \ > + lladdr "${DST_MAC}" nud permanent > + ip -n "${NS_DST}" -6 neigh add fd01::1 dev veth-d0 \ > + lladdr "${RTR_R1_MAC}" nud permanent > +} > + > +test_cache_isolation() > +{ > + RET=0 > + > + # local ping with empty cache: must fail (SID is blackholed) > + if ip netns exec "${NS_RTR}" taskset -c 0 \ > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > + echo "SKIP: local ping succeeded, topology broken" > + exit "${ksft_skip}" > + fi > + > + # forward from ns_src to populate the input cache > + if ! ip netns exec "${NS_SRC}" taskset -c 0 \ > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > + echo "SKIP: forwarded ping failed, topology broken" > + exit "${ksft_skip}" > + fi > + > + # local ping again: must still fail; if the output path reuses > + # the input cache, it bypasses the blackhole and the ping succeeds > + if ip netns exec "${NS_RTR}" taskset -c 0 \ > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > + echo "FAIL: output path used dst cached by input path" > + RET="${ksft_fail}" > + else > + echo "PASS: output path dst_cache is independent" > + fi > + > + return "${RET}" > +} > + We should check it runs as root here (e.g, required for netns creation). if [ "$(id -u)" -ne 0 ]; then echo "SKIP: Need root privileges" exit "${ksft_skip}" fi Otherwise, LGTM: Reviewed-by: Justin Iurman <justin.iurman@gmail.com> > +trap cleanup EXIT > + > +check_prerequisites > +setup > +test_cache_isolation > +exit "${RET}" ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation in seg6 lwtunnel 2026-04-02 18:35 ` Justin Iurman @ 2026-04-03 14:46 ` Andrea Mayer 0 siblings, 0 replies; 6+ messages in thread From: Andrea Mayer @ 2026-04-03 14:46 UTC (permalink / raw) To: Justin Iurman Cc: netdev, davem, edumazet, kuba, pabeni, horms, dsahern, david.lebrun, stefano.salsano, paolo.lungaroni, nicolas.dichtel, linux-kernel, Shuah Khan, linux-kselftest, Andrea Mayer On Thu, 2 Apr 2026 20:35:12 +0200 Justin Iurman <justin.iurman@gmail.com> wrote: > On 4/1/26 20:57, Andrea Mayer wrote: > > Add a selftest that verifies the dst_cache in seg6 lwtunnel is not > > shared between the input (forwarding) and output (locally generated) > > paths. > > > > The test creates three namespaces (ns_src, ns_router, ns_dst) > > connected in a line. An SRv6 encap route on ns_router encapsulates > > traffic destined to cafe::1 with SID fc00::100. The SID is > > reachable only for forwarded traffic (from ns_src) via an ip rule > > matching the ingress interface (iif veth-r0 lookup 100), and > > blackholed in the main table. > > > > The test verifies that: > > > > 1. A packet generated locally on ns_router does not reach > > ns_dst with an empty cache, since the SID is blackholed; > > 2. A forwarded packet from ns_src populates the input cache > > from table 100 and reaches ns_dst; > > 3. A packet generated locally on ns_router still does not > > reach ns_dst after the input cache is populated, > > confirming the output path does not reuse the input > > cache entry. > > > > Both the forwarded and local packets are pinned to the same CPU > > with taskset, since dst_cache is per-cpu. > > > > [snip] > > > > +test_cache_isolation() > > +{ > > + RET=0 > > + > > + # local ping with empty cache: must fail (SID is blackholed) > > + if ip netns exec "${NS_RTR}" taskset -c 0 \ > > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > > + echo "SKIP: local ping succeeded, topology broken" > > + exit "${ksft_skip}" > > + fi > > + > > + # forward from ns_src to populate the input cache > > + if ! ip netns exec "${NS_SRC}" taskset -c 0 \ > > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > > + echo "SKIP: forwarded ping failed, topology broken" > > + exit "${ksft_skip}" > > + fi > > + > > + # local ping again: must still fail; if the output path reuses > > + # the input cache, it bypasses the blackhole and the ping succeeds > > + if ip netns exec "${NS_RTR}" taskset -c 0 \ > > + ping6 -c 1 -W 2 "${DEST}" &>/dev/null; then > > + echo "FAIL: output path used dst cached by input path" > > + RET="${ksft_fail}" > > + else > > + echo "PASS: output path dst_cache is independent" > > + fi > > + > > + return "${RET}" > > +} > > + > > We should check it runs as root here (e.g, required for netns creation). > > if [ "$(id -u)" -ne 0 ]; then > echo "SKIP: Need root privileges" > exit "${ksft_skip}" > fi > Good catch! will fix in v3. > Otherwise, LGTM: > > Reviewed-by: Justin Iurman <justin.iurman@gmail.com> Thanks for the review. Ciao, Andrea ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-03 14:46 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-01 18:57 [PATCH net v2 0/2] seg6: fix dst_cache sharing in seg6 lwtunnel Andrea Mayer 2026-04-01 18:57 ` [PATCH net v2 1/2] seg6: separate dst_cache for input and output paths " Andrea Mayer 2026-04-02 18:30 ` Justin Iurman 2026-04-01 18:57 ` [PATCH net v2 2/2] selftests: seg6: add test for dst_cache isolation " Andrea Mayer 2026-04-02 18:35 ` Justin Iurman 2026-04-03 14:46 ` Andrea Mayer
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox