mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ralf Lici <ralf@mandelbit.com>
To: netdev@vger.kernel.org
Cc: "David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Daniel Gröber" <dxld@darkboxed.org>,
	"Antonio Quartulli" <antonio@mandelbit.com>,
	"Shuah Khan" <shuah@kernel.org>,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC net-next v2 17/18] selftests: net: add ipxlat coverage
Date: Sat, 24 Oct 2026 11:25:27 +0200	[thread overview]
Message-ID: <20261024092532.172005-18-ralf@mandelbit.com> (raw)
In-Reply-To: <20261024092532.172005-1-ralf@mandelbit.com>

Exercise forwarded and local translation, native fallback, target-family
conntrack and NAT, checksumless UDP, route-PMTU fragmentation, conntrack
reassembly followed by refragmentation, PMTU feedback and ICMP errors.

Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
Changes since RFC v1 https://lore.kernel.org/netdev/20260319151230.655687-15-ralf@mandelbit.com/
- Replace the v1 netdevice/YNL suite with an iproute2/LWT namespace
  topology.
- Add coverage for local and forwarded LWT translation, native fallback,
  translated-family conntrack and masquerade, route-PMTU fragmentation,
  reassembly/refragmentation, and PMTU feedback.
- Verify zero-checksum UDPv4 at a real translated-family receiver
  instead of treating a packet capture as proof of successful delivery.
  (Sashiko)
- Keep forwarding, sysctls, routes, and translator state inside
  disposable network namespaces so a failed test cannot leave host
  networking changed. (Sashiko)
- Remove the dedicated UDP generator and produce the required packet
  shapes with existing selftest tools, avoiding the v1 helper's
  undeclared padding bytes. Remove the old fragmented-ICMP capture
  filter whose fixed offset could miss ICMP after an IPv6 Fragment
  Header. (Sashiko)

 tools/testing/selftests/Makefile             |   1 +
 tools/testing/selftests/net/ipxlat/Makefile  |   5 +
 tools/testing/selftests/net/ipxlat/config    |  13 +
 tools/testing/selftests/net/ipxlat/ipxlat.sh | 554 +++++++++++++++++++
 4 files changed, 573 insertions(+)
 create mode 100644 tools/testing/selftests/net/ipxlat/Makefile
 create mode 100644 tools/testing/selftests/net/ipxlat/config
 create mode 100755 tools/testing/selftests/net/ipxlat/ipxlat.sh

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 1198e570898d..339a74d509b8 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -90,6 +90,7 @@ TARGETS += net/af_unix
 TARGETS += net/can
 TARGETS += net/forwarding
 TARGETS += net/hsr
+TARGETS += net/ipxlat
 TARGETS += net/mptcp
 TARGETS += net/netfilter
 TARGETS += net/openvswitch
diff --git a/tools/testing/selftests/net/ipxlat/Makefile b/tools/testing/selftests/net/ipxlat/Makefile
new file mode 100644
index 000000000000..d54a89a84337
--- /dev/null
+++ b/tools/testing/selftests/net/ipxlat/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_PROGS := ipxlat.sh
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/net/ipxlat/config b/tools/testing/selftests/net/ipxlat/config
new file mode 100644
index 000000000000..ced834911830
--- /dev/null
+++ b/tools/testing/selftests/net/ipxlat/config
@@ -0,0 +1,13 @@
+CONFIG_INET=y
+CONFIG_IPV6=y
+CONFIG_LWTUNNEL=y
+CONFIG_NET_IPXLAT=m
+CONFIG_NET_IPXLAT_LWT=m
+CONFIG_NET_NS=y
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_NAT=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_IPV4=y
+CONFIG_NF_TABLES_IPV6=y
+CONFIG_NFT_NAT=m
+CONFIG_VETH=m
diff --git a/tools/testing/selftests/net/ipxlat/ipxlat.sh b/tools/testing/selftests/net/ipxlat/ipxlat.sh
new file mode 100755
index 000000000000..955a50ac8231
--- /dev/null
+++ b/tools/testing/selftests/net/ipxlat/ipxlat.sh
@@ -0,0 +1,554 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# IPXLAT lightweight-tunnel selftests
+#
+# Copyright (C) 2026- Mandelbit SRL
+# Copyright (C) 2026- Daniel Gröber <dxld@darkboxed.org>
+#
+# Author: Antonio Quartulli <antonio@mandelbit.com>
+#         Daniel Gröber <dxld@darkboxed.org>
+#         Ralf Lici <ralf@mandelbit.com>
+#
+#   ipxlat_ns4 (IPv4 host) --- ipxlat_nsx --- ipxlat_ns6 (IPv6 host)
+
+set -o pipefail
+
+TEST_DIR=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")
+readonly TEST_DIR
+source "$TEST_DIR/../lib.sh"
+
+# All addresses come from documentation ranges. The EAM replaces
+# 198.51.100.0/24 with 2001:db8:200::/120 and preserves only the final host
+# byte. V4_XLAT therefore becomes V6_EAM_XLAT, while V4_NEAR becomes
+# V6_EAM_NEAR. EAM takes precedence in both directions.
+#
+# Addresses outside the EAM use the RFC 6052 fallback. With a /96 prefix it
+# appends the complete 32-bit IPv4 address, so V4_REMOTE and V4_PMTU become
+# V6_REMOTE and V6_PMTU. V6_NATIVE is deliberately outside both mappings and
+# exists only to exercise native output when ipxlat declines a packet.
+#
+# nsx also needs an IPv6 gateway address. We deliberately choose V6_XLAT as
+# the RFC 6052 form of V4_XLAT so that, if the kernel uses it as the source of
+# an ICMPv6 error, translation produces the meaningful V4_XLAT source instead
+# of the 192.0.0.8 dummy fallback. V6_XLAT is not the normal 4-to-6 result for
+# V4_XLAT: EAM still produces V6_EAM_XLAT. Both IPv6 addresses map back to
+# V4_XLAT, each through its respective algorithm.
+
+readonly V4_NEAR_PREFIX=198.51.100.0/24
+readonly V4_XLAT=198.51.100.1
+readonly V4_NEAR=198.51.100.2
+readonly V4_REMOTE_PREFIX=192.0.2.0/24
+readonly V4_REMOTE=192.0.2.33
+readonly V4_PMTU=192.0.2.34
+
+readonly V6_RFC6052_PREFIX=2001:db8:100::/96
+readonly V6_XLAT=2001:db8:100::c633:6401
+readonly V6_REMOTE=2001:db8:100::c000:221
+readonly V6_PMTU=2001:db8:100::c000:222
+readonly V6_NATIVE=2001:db8:100:0:1::1
+readonly V6_EAM_PREFIX=2001:db8:200::/120
+readonly V6_EAM_XLAT=2001:db8:200::1
+readonly V6_EAM_NEAR=2001:db8:200::2
+
+readonly IPERF_PORT=5201
+readonly UDP_PORT=5202
+
+BG_PIDS=()
+TMP_FILES=()
+
+# setup_ns assigns these variables indirectly
+ipxlat_ns4=
+ipxlat_nsx=
+ipxlat_ns6=
+
+cleanup()
+{
+	local file pid
+
+	for pid in "${BG_PIDS[@]}"; do
+		kill "$pid" 2>/dev/null || true
+		wait "$pid" 2>/dev/null || true
+	done
+	for file in "${TMP_FILES[@]}"; do
+		rm -f "$file"
+	done
+	cleanup_all_ns
+}
+
+counter_nonzero()
+{
+	local packets
+	local ns=$1
+	local family=$2
+	local table=$3
+	local counter=$4
+
+	packets=$(ip netns exec "$ns" nft list counter \
+		  "$family" "$table" "$counter" |
+		  awk '/packets/ { print $2; exit }') || return 1
+	((packets > 0))
+}
+
+tcp_listener_ready()
+{
+	[[ -n $(ip netns exec "$ipxlat_ns4" ss -Hln \
+		 "sport = :$IPERF_PORT" 2>/dev/null) ]]
+}
+
+udp_listener_ready()
+{
+	[[ -n $(ip netns exec "$ipxlat_ns6" ss -Hlun \
+		 "sport = :$UDP_PORT" 2>/dev/null) ]]
+}
+
+file_has_size()
+{
+	local file=$1
+	local expected=$2
+
+	[[ -f $file ]] && [[ $(stat -c %s "$file") -eq $expected ]]
+}
+
+pmtu_is_1260()
+{
+	ip -n "$ipxlat_ns4" route get "$V4_PMTU" |
+		grep -Eq 'mtu[[:space:]]+1260'
+}
+
+setup_topology()
+{
+	# The IPv4-only host reaches the IPv6-only host through the translator.
+	# EAM gives IPv6 identities to the IPv4 endpoints, while RFC 6052 gives
+	# IPv4 identities to the IPv6 endpoints. The test-only V6_NATIVE
+	# address is omitted from the operational topology below.
+	#
+	# Topology:
+	# +------------------------------------------------------------------+
+	# |                            IPXLAT_NS4                            |
+	# |                                                                  |
+	# |                          +------------+                          |
+	# |                       v4 | V4_NEAR/24 |                          |
+	# +------------------------------------------------------------------+
+	#                                  |
+	#                                  |
+	# +------------------------------------------------------------------+
+	# |                       x4 | V4_XLAT/24 |                          |
+	# |                          +------------+                          |
+	# |                                                                  |
+	# |                            IPXLAT_NSX                            |
+	# |                                                                  |
+	# |                       +-----------------+                        |
+	# |                       | V6_XLAT/64      |                        |
+	# |                    x6 | V6_EAM_XLAT/128 |                        |
+	# +------------------------------------------------------------------+
+	#                                  |
+	#                                  |
+	# +------------------------------------------------------------------+
+	# |                    v6 | V6_REMOTE/64    |                        |
+	# |                       | V6_PMTU/128     |                        |
+	# |                       | V6_NATIVE/128   |                        |
+	# |                       +-----------------+                        |
+	# |                                                                  |
+	# |                            IPXLAT_NS6                            |
+	# +------------------------------------------------------------------+
+
+	ip link add v4 type veth peer name x4
+	ip link set v4 netns "$ipxlat_ns4"
+	ip link set x4 netns "$ipxlat_nsx"
+	ip link add v6 type veth peer name x6
+	ip link set v6 netns "$ipxlat_ns6"
+	ip link set x6 netns "$ipxlat_nsx"
+
+	ip -n "$ipxlat_ns4" addr add "$V4_NEAR/24" dev v4
+	ip -n "$ipxlat_nsx" addr add "$V4_XLAT/24" dev x4
+
+	# These are selftest controls, not ipxlat requirements: nodad avoids
+	# asynchronous address setup, while noprefixroute keeps the EAM address
+	# from creating a main-table route that would bypass the tested route.
+	ip -n "$ipxlat_nsx" -6 addr add "$V6_XLAT/64" dev x6 nodad
+	ip -n "$ipxlat_nsx" -6 addr add "$V6_EAM_XLAT/128" dev x6 nodad \
+		noprefixroute
+	ip -n "$ipxlat_ns6" -6 addr add "$V6_REMOTE/64" dev v6 nodad
+	ip -n "$ipxlat_ns6" -6 addr add "$V6_PMTU/128" dev v6 nodad
+	# avoid waiting for DAD on the isolated fallback-test address
+	ip -n "$ipxlat_ns6" -6 addr add "$V6_NATIVE/128" dev v6 nodad
+
+	ip -n "$ipxlat_ns4" link set v4 up
+	ip -n "$ipxlat_nsx" link set x4 up
+	ip -n "$ipxlat_nsx" link set x6 up
+	ip -n "$ipxlat_ns6" link set v6 up
+
+	ip netns exec "$ipxlat_nsx" sysctl -qw net.ipv4.ip_forward=1
+	ip netns exec "$ipxlat_nsx" sysctl -qw net.ipv6.conf.all.forwarding=1
+
+	ip -n "$ipxlat_ns4" route add default via "$V4_XLAT"
+	# explicitly use the ordinary endpoint for non-PMTU traffic since ns6
+	# has two addresses
+	ip -n "$ipxlat_ns6" -6 route add "$V6_EAM_PREFIX" via "$V6_XLAT" \
+		src "$V6_REMOTE"
+
+	# constrain only the PMTU endpoint while all other traffic uses the
+	# normal 1500-byte veth MTU
+	ip -n "$ipxlat_nsx" -6 route add "$V6_PMTU/128" dev x6 mtu 1280
+
+	ip -n "$ipxlat_nsx" route add "$V4_REMOTE_PREFIX" \
+		encap ipxlat rfc6052-prefix6 "$V6_RFC6052_PREFIX" \
+		eam "$V4_NEAR_PREFIX" "$V6_EAM_PREFIX" dev x6
+
+	ip -n "$ipxlat_nsx" -6 route add "$V6_EAM_PREFIX" \
+		encap ipxlat rfc6052-prefix6 "$V6_RFC6052_PREFIX" \
+		eam "$V4_NEAR_PREFIX" "$V6_EAM_PREFIX" dev x4
+	# address assignment creates a metric-zero kernel local route so remove
+	# it before adding the LWT
+	ip -n "$ipxlat_nsx" -6 route del local "$V6_EAM_XLAT/128" table local
+	ip -n "$ipxlat_nsx" -6 route add local "$V6_EAM_XLAT/128" table local \
+		encap ipxlat rfc6052-prefix6 "$V6_RFC6052_PREFIX" \
+		eam "$V4_NEAR_PREFIX" "$V6_EAM_PREFIX" dev x6
+}
+
+warm_up_neighbors()
+{
+	# keep first-packet ARP/NDP delays out of the translation assertions
+	ip netns exec "$ipxlat_nsx" ping -c 1 -W 2 "$V4_NEAR" \
+		>/dev/null 2>&1
+	ip netns exec "$ipxlat_nsx" ping -6 -c 1 -W 2 "$V6_REMOTE" \
+		>/dev/null 2>&1
+}
+
+setup_refragment_nft()
+{
+	# Enable conntrack only for IPv6 here. If IPv4 conntrack were already
+	# active, it would reassemble the source frags before ipxlat saw them.
+	ip netns exec "$ipxlat_nsx" nft -f - <<EOF
+add table ip6 ipxlat_refragment
+add counter ip6 ipxlat_refragment tracked_udp
+add chain ip6 ipxlat_refragment postrouting { type filter hook postrouting \
+priority filter; policy accept; }
+add rule ip6 ipxlat_refragment postrouting ip6 saddr $V6_EAM_NEAR \
+ip6 daddr $V6_PMTU udp dport $UDP_PORT ct state { new, established } \
+counter name tracked_udp
+EOF
+}
+
+setup_nft()
+{
+	# count the translated IPv4 errors returned to the IPv4 host
+	ip netns exec "$ipxlat_ns4" nft -f - <<EOF
+add table ip ipxlat_test
+add counter ip ipxlat_test frag_needed
+add counter ip ipxlat_test port_unreachable
+add chain ip ipxlat_test input { type filter hook input priority filter; policy accept; }
+add rule ip ipxlat_test input icmp type destination-unreachable icmp code frag-needed counter name frag_needed
+add rule ip ipxlat_test input icmp type destination-unreachable icmp code port-unreachable counter name port_unreachable
+EOF
+
+	# count the translated IPv6 error returned to the IPv6 host
+	ip netns exec "$ipxlat_ns6" nft -f - <<EOF
+add table ip6 ipxlat_test
+add counter ip6 ipxlat_test port_unreachable
+add chain ip6 ipxlat_test input { type filter hook input priority filter; policy accept; }
+add rule ip6 ipxlat_test input icmpv6 type destination-unreachable icmpv6 code port-unreachable counter name port_unreachable
+EOF
+
+	# count locally translated packets that reach IPv6 LOCAL_OUT
+	ip netns exec "$ipxlat_nsx" nft -f - <<EOF
+add table ip6 ipxlat_output
+add counter ip6 ipxlat_output translated_echo
+add chain ip6 ipxlat_output output { type filter hook output priority filter; policy accept; }
+add rule ip6 ipxlat_output output ip6 saddr $V6_EAM_XLAT ip6 daddr $V6_REMOTE icmpv6 type echo-request counter name translated_echo
+EOF
+}
+
+setup_nat64_nft()
+{
+	# MASQUERADE proves that translated IPv4 traffic reaches conntrack and
+	# POSTROUTING. Install it after the refragmentation test because enabling
+	# IPv4 conntrack earlier would reassemble its source fragments.
+	ip netns exec "$ipxlat_nsx" nft -f - <<EOF
+add table ip ipxlat_nat
+add counter ip ipxlat_nat translated_tcp
+add chain ip ipxlat_nat postrouting { type nat hook postrouting priority srcnat; policy accept; }
+add rule ip ipxlat_nat postrouting ip saddr $V4_REMOTE ip daddr $V4_NEAR tcp dport $IPERF_PORT counter name translated_tcp masquerade
+EOF
+}
+
+# translate a forwarded IPv4 echo request to IPv6 and its reply back to IPv4
+test_forwarded_echo()
+{
+	RET=0
+	ip netns exec "$ipxlat_ns4" ping -c 1 -W 2 "$V4_REMOTE" \
+		>/dev/null 2>&1
+	check_err $? "IPv4 echo did not cross the translator"
+	log_test "forwarded ICMP translation"
+}
+
+# require locally generated IPv4 traffic to pass through IPv6 LOCAL_OUT
+test_local_output()
+{
+	RET=0
+	ip netns exec "$ipxlat_nsx" ping -I "$V4_XLAT" -c 1 -W 2 \
+		"$V4_REMOTE" >/dev/null 2>&1
+	check_err $? "locally originated IPv4 echo failed"
+	slowwait 2 counter_nonzero "$ipxlat_nsx" ip6 ipxlat_output \
+		translated_echo
+	check_err $? "translated packet did not traverse IPv6 LOCAL_OUT"
+	log_test "locally originated CLAT traffic"
+}
+
+# return valid packets unsupported by SIIT to the original route
+test_native_fallback()
+{
+	RET=0
+
+	# Test native input fallback
+
+	# remove the cached ethernet address for V6_REMOTE from nsx so that the
+	# next packet must perform IPv6 neighbor discovery
+	ip -n "$ipxlat_nsx" -6 neigh flush to "$V6_REMOTE" dev x6 \
+		>/dev/null 2>&1
+
+	# The sequence here is:
+	# 1. nsx sends a neighbor solicitation;
+	# 2. ns6 replies with a neighbor advertisement;
+	# 3. because the solicitation used V6_EAM_XLAT as its source, the
+	#    advertisement is addressed to V6_EAM_XLAT (the local route for
+	#    V6_XLAT is an ordinary IPv6 route, it does not carry ipxlat);
+	# 4. the local route for V6_EAM_XLAT carries the ipxlat input LWT, so
+	#    the advertisement enters ipxlat_lwt_input;
+	# 5. both NA endpoints are translatable, but the ICMPv6 type is not so
+	#    the input cb restores the original metadata and calls orig_input;
+	# 6. normal IPv6 neighbor discovery then consumes the unchanged
+	#    advertisement and updates the neighbor table;
+	# 7. the ping itself is allowed to fail because its echo reply is
+	#    addressed to V6_EAM_XLAT and, unlike the NA, an echo reply is
+	#    translatable, so the input LWT translates it to IPv4 instead of
+	#    delivering it to the IPv6 ping socket.
+	ip netns exec "$ipxlat_nsx" ping -6 -I "$V6_EAM_XLAT" \
+		-c 1 -W 1 "$V6_REMOTE" >/dev/null 2>&1 || true
+
+	# if the neighbor entry exists in one of these usable states, the NA
+	# must have passed through native IPv6 input (not swallowed by ipxlat)
+	ip -n "$ipxlat_nsx" -6 neigh show "$V6_REMOTE" dev x6 |
+		grep -Eq '\b(REACHABLE|STALE|DELAY|PROBE)\b'
+	check_err $? "Neighbor Advertisement was intercepted by the local LWT"
+
+	# Test native output fallback
+
+	# V6_XLAT was already reachable through the connected /64, but this
+	# /128 route takes precedence and forces locally generated traffic for
+	# that address through ipxlat_lwt_output
+	ip -n "$ipxlat_ns6" -6 route add "$V6_XLAT/128" \
+		encap ipxlat rfc6052-prefix6 "$V6_RFC6052_PREFIX" \
+		eam "$V4_NEAR_PREFIX" "$V6_EAM_PREFIX" dev v6
+	check_err $? "could not install native-output fallback route"
+
+	# The source is outside both mappings, so native output must send the
+	# unchanged packet across the existing IPv6 link.
+	#
+	# The sequence is:
+	# 1. ipxlat_prepare validates the IPv6 packet returns -EOPNOTSUPP
+	#    because it cannot convert V6_NATIVE;
+	# 2. the output callback restores the skb metadata and calls the
+	#    route's saved orig_output callback;
+	# 3. native IPv6 sends the echo request over the existing veth link.
+	#
+	# nsx owns V6_XLAT, whose normal local route does not carry ipxlat, so
+	# it answers normally. The reply returns to the ordinary local route
+	# for V6_NATIVE, and the ping succeeds.
+	ip netns exec "$ipxlat_ns6" ping -6 -I "$V6_NATIVE" \
+		-c 1 -W 1 "$V6_XLAT" >/dev/null 2>&1
+	check_err $? "untranslatable packet did not cross native output"
+	ip -n "$ipxlat_ns6" -6 route del "$V6_XLAT/128" >/dev/null 2>&1 || true
+	log_test "native input and output fallback"
+}
+
+# run IPv6-initiated TCP through translation and target-family MASQUERADE
+test_stateful_nat64()
+{
+	local server_pid
+
+	RET=0
+	timeout 10 ip netns exec "$ipxlat_ns4" iperf3 -s -1 -p "$IPERF_PORT" \
+		>/dev/null 2>&1 &
+	server_pid=$!
+	BG_PIDS+=("$server_pid")
+	slowwait 2 tcp_listener_ready
+	check_err $? "iperf3 server did not start"
+
+	timeout 10 ip netns exec "$ipxlat_ns6" iperf3 -6 \
+		-c "$V6_EAM_NEAR" -B "$V6_REMOTE" -p "$IPERF_PORT" \
+		-n 256K >/dev/null 2>&1
+	check_err $? "TCP transfer through stateful NAT64 failed"
+	wait "$server_pid"
+	check_err $? "iperf3 server failed"
+
+	slowwait 2 counter_nonzero "$ipxlat_nsx" ip ipxlat_nat translated_tcp
+	check_err $? "translated IPv4 traffic did not reach MASQUERADE"
+	log_test "target-family conntrack and NAT"
+}
+
+# require a large checksumless IPv4 UDP datagram to survive IPv6 fragmentation
+test_zero_checksum_postfragment()
+{
+	local target="UDP4-DATAGRAM:$V4_PMTU:$UDP_PORT"
+	local payload_len=1400
+	local receiver_pid
+	local output
+
+	RET=0
+	output=$(mktemp)
+	TMP_FILES+=("$output")
+
+	timeout 4 ip netns exec "$ipxlat_ns6" socat -u \
+		"UDP6-RECVFROM:$UDP_PORT,reuseaddr" \
+		"CREATE:$output" >/dev/null 2>&1 &
+	receiver_pid=$!
+	BG_PIDS+=("$receiver_pid")
+	slowwait 2 udp_listener_ready
+	check_err $? "IPv6 UDP receiver did not start"
+
+	head -c "$payload_len" /dev/zero |
+		ip netns exec "$ipxlat_ns4" socat -u - \
+		"$target,so-no-check=1,ip-mtu-discover=0"
+	# SO_NO_CHECK creates a real CHECKSUM_NONE packet and PMTUDISC_DONT
+	# leaves DF clear, so the 1280-byte translated route must fragment the
+	# complete IPv6 datagram.
+	check_err $? "checksumless IPv4 UDP send failed"
+	slowwait 3 file_has_size "$output" "$payload_len"
+	check_err $? "translated IPv6 UDP datagram was not delivered intact"
+
+	kill "$receiver_pid" 2>/dev/null || true
+	wait "$receiver_pid" 2>/dev/null || true
+	log_test "UDP zero checksum and route-PMTU fragmentation"
+}
+
+# preserve local fragmentation ownership across target-family conntrack defrag
+test_conntrack_refragment()
+{
+	local target="UDP4-DATAGRAM:$V4_PMTU:$UDP_PORT"
+	local payload_len=1900
+	local receiver_pid
+	local output
+
+	RET=0
+	output=$(mktemp)
+	TMP_FILES+=("$output")
+
+	timeout 4 ip netns exec "$ipxlat_ns6" socat -u \
+		"UDP6-RECVFROM:$UDP_PORT,reuseaddr" \
+		"CREATE:$output" >/dev/null 2>&1 &
+	receiver_pid=$!
+	BG_PIDS+=("$receiver_pid")
+	slowwait 2 udp_listener_ready
+	check_err $? "IPv6 UDP receiver did not start"
+
+	# The IPv4 sender first splits the datagram at 1400 bytes. Translation
+	# produces IPv6 fragments larger than the 1280-byte target route. IPv6
+	# conntrack reassembles them in LOCAL_OUT, then IPv6 output must
+	# fragment the complete datagram again for that route.
+	ip -n "$ipxlat_ns4" link set v4 mtu 1400
+	head -c "$payload_len" /dev/zero |
+		ip netns exec "$ipxlat_ns4" socat -u - \
+		"$target,ip-mtu-discover=0"
+	check_err $? "fragmented IPv4 UDP send failed"
+	ip -n "$ipxlat_ns4" link set v4 mtu 1500
+
+	slowwait 3 file_has_size "$output" "$payload_len"
+	check_err $? "conntrack-defragmented IPv6 datagram was not delivered"
+	slowwait 2 counter_nonzero "$ipxlat_nsx" ip6 ipxlat_refragment \
+		tracked_udp
+	check_err $? "reassembled IPv6 datagram did not acquire conntrack state"
+
+	kill "$receiver_pid" 2>/dev/null || true
+	wait "$receiver_pid" 2>/dev/null || true
+	log_test "conntrack defrag and target-route refragmentation"
+}
+
+# translate IPv6 PTB feedback and install the resulting IPv4 PMTU exception
+test_pmtu_feedback()
+{
+	RET=0
+	ip netns exec "$ipxlat_ns4" ping -M "do" -s 1300 -c 1 -W 1 \
+		"$V4_PMTU" >/dev/null 2>&1 || true
+
+	slowwait 3 counter_nonzero "$ipxlat_ns4" ip ipxlat_test frag_needed
+	check_err $? "translated IPv4 Fragmentation Needed was not received"
+	slowwait 3 pmtu_is_1260
+	check_err $? "IPv4 PMTU was not updated to 1260"
+	log_test "translated-family PMTU feedback"
+}
+
+# translate an IPv6 Port Unreachable response back to the IPv4 sender
+test_icmp_error_46()
+{
+	RET=0
+	printf x | ip netns exec "$ipxlat_ns4" socat -u - \
+		"UDP4-DATAGRAM:$V4_REMOTE:$UDP_PORT" >/dev/null 2>&1
+	check_err $? "IPv4 UDP trigger failed"
+	slowwait 3 counter_nonzero "$ipxlat_ns4" ip ipxlat_test \
+		port_unreachable
+	check_err $? "translated IPv4 Port Unreachable was not received"
+	log_test "ICMP error translation 4 to 6"
+}
+
+# translate an IPv4 Port Unreachable response back to the IPv6 sender
+test_icmp_error_64()
+{
+	RET=0
+	printf x | ip netns exec "$ipxlat_ns6" socat -u - \
+		"UDP6-DATAGRAM:[$V6_EAM_NEAR]:$UDP_PORT" >/dev/null 2>&1
+	check_err $? "IPv6 UDP trigger failed"
+	slowwait 3 counter_nonzero "$ipxlat_ns6" ip6 ipxlat_test \
+		port_unreachable
+	check_err $? "translated IPv6 Port Unreachable was not received"
+	log_test "ICMP error translation 6 to 4"
+}
+
+if [[ $(id -u) -ne 0 ]]; then
+	echo "SKIP: this test needs root privileges"
+	exit "$ksft_skip"
+fi
+
+require_command ip
+require_command iperf3
+require_command nft
+require_command ping
+require_command socat
+require_command ss
+require_command timeout
+
+if ! grep -qw ipxlat < <(ip route help 2>&1); then
+	echo "SKIP: iproute2 does not support the ipxlat LWT encapsulation"
+	exit "$ksft_skip"
+fi
+
+trap cleanup EXIT
+
+set -e
+setup_ns ipxlat_ns4 ipxlat_nsx ipxlat_ns6
+setup_topology
+warm_up_neighbors
+setup_nft
+
+# test_conntrack_refragment requires setup_refragment_nft
+setup_refragment_nft
+set +e
+test_conntrack_refragment
+ip netns exec "$ipxlat_nsx" nft delete table ip6 ipxlat_refragment
+
+# the rest of the tests require setup_nat64_nft
+set -e
+setup_nat64_nft
+set +e
+
+test_forwarded_echo
+test_local_output
+test_native_fallback
+test_stateful_nat64
+test_zero_checksum_postfragment
+test_pmtu_feedback
+test_icmp_error_46
+test_icmp_error_64
+
+exit "$EXIT_STATUS"
-- 
2.55.0


  parent reply	other threads:[~2026-10-24  9:26 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24  9:25 [RFC net-next v2 00/18] net: introduce ipxlat stateless IPv4/IPv6 translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 01/18] lwtunnel: track callback dispatch separately from return value Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 02/18] ipv4/ipv6: add LWT receive helpers Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 03/18] ipv6: allow output fragmentation to reuse a Fragment Header Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 04/18] netfilter: preserve IPv6 refragmentation state across reassembly Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 05/18] net: ipxlat: add engine skeleton and configuration model Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 06/18] net: ipxlat: add RFC 6052 and EAM address translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 07/18] net: ipxlat: add packet metadata and header helpers Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 08/18] net: ipxlat: add IPv4 packet validation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 09/18] net: ipxlat: add IPv6 " Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 10/18] net: ipxlat: add checksum and offload helpers Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 11/18] net: ipxlat: add IPv4/IPv6 header and transport translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 12/18] net: ipxlat: add translation preparation and dispatch Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 13/18] net: ipxlat: add translator-generated ICMP errors Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 14/18] net: ipxlat: add ICMP informational translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 15/18] net: ipxlat: add ICMP error and quoted-packet translation Ralf Lici
2026-10-24  9:25 ` [RFC net-next v2 16/18] net: ipxlat: add lightweight tunnel attachment Ralf Lici
2026-10-24  9:25 ` Ralf Lici [this message]
2026-10-24  9:25 ` [RFC net-next v2 18/18] docs: networking: add ipxlat guide Ralf Lici

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261024092532.172005-18-ralf@mandelbit.com \
    --to=ralf@mandelbit.com \
    --cc=antonio@mandelbit.com \
    --cc=davem@davemloft.net \
    --cc=dxld@darkboxed.org \
    --cc=edumazet@kernel.org \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®