]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests: seg6: add test for dst_cache isolation in seg6 lwtunnel
authorAndrea Mayer <andrea.mayer@uniroma2.it>
Sat, 4 Apr 2026 00:44:05 +0000 (02:44 +0200)
committerJakub Kicinski <kuba@kernel.org>
Wed, 8 Apr 2026 03:20:56 +0000 (20:20 -0700)
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>
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
Reviewed-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Reviewed-by: Justin Iurman <justin.iurman@gmail.com>
Link: https://patch.msgid.link/20260404004405.4057-3-andrea.mayer@uniroma2.it
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
tools/testing/selftests/net/Makefile
tools/testing/selftests/net/srv6_iptunnel_cache.sh [new file with mode: 0755]

index 605c54c0e8a35bfa3acf21adf6216d67d9785115..c709523c99c6bc81f1bace37febb507a1edabdad 100644 (file)
@@ -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 (executable)
index 0000000..62638ab
--- /dev/null
@@ -0,0 +1,197 @@
+#!/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 ip &>/dev/null; then
+               echo "SKIP: ip tool not found"
+               exit "${ksft_skip}"
+       fi
+
+       if ! command -v ping &>/dev/null; then
+               echo "SKIP: ping not found"
+               exit "${ksft_skip}"
+       fi
+
+       if ! command -v sysctl &>/dev/null; then
+               echo "SKIP: sysctl not found"
+               exit "${ksft_skip}"
+       fi
+
+       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 \
+                       ping -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 \
+                       ping -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 \
+                       ping -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}"
+}
+
+if [ "$(id -u)" -ne 0 ]; then
+       echo "SKIP: Need root privileges"
+       exit "${ksft_skip}"
+fi
+
+trap cleanup EXIT
+
+check_prerequisites
+setup
+test_cache_isolation
+exit "${RET}"