Add a selftest for the srl2 Ethernet pseudowire device exercising the
full L2 VPN data path: srl2 for encapsulation and End.DT2U for
decapsulation, connected through a Linux bridge.

The test verifies IPv4/IPv6 host-to-host and host-to-gateway
connectivity over a two-router topology.

Cc: Shuah Khan <[email protected]>
Cc: [email protected]
Signed-off-by: Andrea Mayer <[email protected]>
---
 tools/testing/selftests/net/Makefile          |   1 +
 tools/testing/selftests/net/config            |   1 +
 .../selftests/net/srv6_srl2_l2vpn_test.sh     | 621 ++++++++++++++++++
 3 files changed, 623 insertions(+)
 create mode 100755 tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh

diff --git a/tools/testing/selftests/net/Makefile 
b/tools/testing/selftests/net/Makefile
index 6bced3ed798b..d2301387b21c 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -92,6 +92,7 @@ TEST_PROGS := \
        srv6_end_x_next_csid_l3vpn_test.sh \
        srv6_hencap_red_l3vpn_test.sh \
        srv6_hl2encap_red_l2vpn_test.sh \
+       srv6_srl2_l2vpn_test.sh \
        stress_reuseport_listen.sh \
        tcp_fastopen_backup_key.sh \
        test_bpf.sh \
diff --git a/tools/testing/selftests/net/config 
b/tools/testing/selftests/net/config
index 2a390cae41bf..77d5c7941969 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -48,6 +48,7 @@ CONFIG_IPV6_ROUTER_PREF=y
 CONFIG_IPV6_RPL_LWTUNNEL=y
 CONFIG_IPV6_SEG6_LWTUNNEL=y
 CONFIG_IPV6_SIT=y
+CONFIG_IPV6_SRL2=m
 CONFIG_IPV6_VTI=y
 CONFIG_IPVLAN=m
 CONFIG_IPVTAP=m
diff --git a/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh 
b/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
new file mode 100755
index 000000000000..eefc1274e139
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
@@ -0,0 +1,621 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Andrea Mayer <[email protected]>
+#
+# This script tests the full SRv6 L2 VPN data path using the srl2
+# virtual Ethernet device for L2 frame encapsulation and the End.DT2U
+# behavior (RFC 8986, Section 4.11) for decapsulation.
+#
+# This test exercises the full SRv6 L2 VPN data path using the srl2
+# device as the TX-side encapsulator. Each SRv6 router uses a bridge
+# (br0) with two ports:
+#   - veth-hs: connects to the host
+#   - srl2-0: SRv6 L2 tunnel device for encap/decap
+#
+# TX path: the host sends an L2 frame via veth-hs. The bridge forwards
+# the frame to srl2-0 (L2 forwarding based on dst MAC). srl2-0
+# encapsulates the frame in IPv6+SRH and transmits.
+#
+# RX path: an SRv6 packet arrives carrying an inner Ethernet frame.
+# End.DT2U decapsulates and delivers the frame on srl2-0 via
+# netif_rx(). Since srl2-0 is a bridge port, the bridge performs MAC
+# learning and L2 forwarding to deliver the frame to veth-hs.
+#
+# Note: no static MAC addresses or neighbor entries are needed here.
+# ARP works naturally through the
+# bridge and the SRv6 tunnel: ARP requests are broadcast, flooded by
+# the bridge to srl2-0, encapsulated, decapsulated by End.DT2U on the
+# remote side, and flooded to the destination host.
+#
+# Topology:
+#
+#              cafe::1                                cafe::2
+#             10.0.0.1                               10.0.0.2
+#            +--------+                             +--------+
+#            |        |                             |        |
+#            |  hs-1  |                             |  hs-2  |
+#            |        |                             |        |
+#            +---+----+                             +----+---+
+#   cafe::/64    |                                       |      cafe::/64
+# 10.0.0.0/24    |                                       |    10.0.0.0/24
+#          +-----+------+                         +------+-----+
+#          |   veth-hs  |                         |  veth-hs   |
+#          |      |     |   fcf0:0:1:2::/64       |     |      |
+#          |    br0     +-------------------------+   br0      |
+#          |      |     |                         |     |      |
+#          |   srl2-0   |                         |  srl2-0    |
+#          |    rt-1    |                         |   rt-2     |
+#          +------------+                         +------------+
+#
+#
+# Every fcf0:0:x:y::/64 network interconnects the SRv6 routers rt-x with
+# rt-y in the IPv6 operator network.
+#
+# Local SID table
+# ===============
+#
+# Each SRv6 router is configured with a Local SID table in which SIDs are
+# stored. Considering the given SRv6 router rt-x, the following SID is
+# configured in the Local SID table:
+#
+#   Local SID table for SRv6 router rt-x
+#   +-----------------------------------------------------------+
+#   |fcff:x::d20 is associated with the SRv6 End.DT2U behavior |
+#   +-----------------------------------------------------------+
+#
+# SRv6 L2 encapsulation
+# =====================
+#
+# Each router's srl2-0 device is configured with a SID list pointing to
+# the remote router's End.DT2U SID:
+#
+#   rt-1 srl2-0: segs fcff:2::d20  (encap towards rt-2)
+#   rt-2 srl2-0: segs fcff:1::d20  (encap towards rt-1)
+#
+# Each SID list consists of only one SID. The srl2 device encapsulates
+# the L2 frame in an outer IPv6 header with an SRH containing the
+# segment list.
+#
+
+source lib.sh
+
+readonly DUMMY_DEVNAME="dum0"
+readonly SRL2_DEVNAME="srl2-0"
+readonly RT2HS_DEVNAME="veth-hs"
+readonly BRIDGE_DEVNAME="br0"
+readonly HS_VETH_NAME="veth0"
+readonly LOCALSID_TABLE_ID=90
+readonly IPv6_RT_NETWORK=fcf0:0
+readonly IPv6_HS_NETWORK=cafe
+readonly IPv4_HS_NETWORK=10.0.0
+readonly VPN_LOCATOR_SERVICE=fcff
+readonly DT2U_FUNC=0d20
+
+PING_TIMEOUT_SEC=4
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+ROUTERS=''
+HOSTS=''
+
+SETUP_ERR=1
+
+ret=${ksft_skip}
+nsuccess=0
+nfail=0
+
+log_test()
+{
+       local rc="$1"
+       local expected="$2"
+       local msg="$3"
+
+       if [ "${rc}" -eq "${expected}" ]; then
+               nsuccess=$((nsuccess+1))
+               printf "\n    TEST: %-60s  [ OK ]\n" "${msg}"
+       else
+               ret=1
+               nfail=$((nfail+1))
+               printf "\n    TEST: %-60s  [FAIL]\n" "${msg}"
+               if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+                       echo
+                       echo "hit enter to continue, 'q' to quit"
+                       read a
+                       [ "$a" = "q" ] && exit 1
+               fi
+       fi
+}
+
+print_log_test_results()
+{
+       printf "\nTests passed: %3d\n" "${nsuccess}"
+       printf "Tests failed: %3d\n"   "${nfail}"
+
+       if [ "${ret}" -ne 1 ]; then
+               ret=0
+       fi
+}
+
+log_section()
+{
+       echo
+       echo 
"################################################################################"
+       echo "TEST SECTION: $*"
+       echo 
"################################################################################"
+}
+
+test_command_or_ksft_skip()
+{
+       local cmd="$1"
+
+       if [ ! -x "$(command -v "${cmd}")" ]; then
+               echo "SKIP: Could not run test without \"${cmd}\" tool";
+               exit "${ksft_skip}"
+       fi
+}
+
+get_rtname()
+{
+       local rtid="$1"
+
+       echo "rt_${rtid}"
+}
+
+get_hsname()
+{
+       local hsid="$1"
+
+       echo "hs_${hsid}"
+}
+
+create_router()
+{
+       local rtid="$1"
+       local nsname
+
+       nsname="$(get_rtname "${rtid}")"
+       setup_ns "${nsname}"
+}
+
+create_host()
+{
+       local hsid="$1"
+       local nsname
+
+       nsname="$(get_hsname "${hsid}")"
+       setup_ns "${nsname}"
+}
+
+cleanup()
+{
+       cleanup_all_ns
+
+       if [ "${SETUP_ERR}" -ne 0 ]; then
+               echo "SKIP: Setting up the testing environment failed"
+               exit "${ksft_skip}"
+       fi
+
+       exit "${ret}"
+}
+
+add_link_rt_pairs()
+{
+       local rt="$1"
+       local rt_neighs="$2"
+       local neigh
+       local nsname
+       local neigh_nsname
+
+       eval nsname=\${$(get_rtname "${rt}")}
+
+       for neigh in ${rt_neighs}; do
+               eval neigh_nsname=\${$(get_rtname "${neigh}")}
+
+               ip link add "veth-rt-${rt}-${neigh}" netns "${nsname}" \
+                       type veth peer name "veth-rt-${neigh}-${rt}" \
+                       netns "${neigh_nsname}"
+       done
+}
+
+get_network_prefix()
+{
+       local rt="$1"
+       local neigh="$2"
+       local p="${rt}"
+       local q="${neigh}"
+
+       if [ "${p}" -gt "${q}" ]; then
+               p="${q}"; q="${rt}"
+       fi
+
+       echo "${IPv6_RT_NETWORK}:${p}:${q}"
+}
+
+setup_rt_networking()
+{
+       local rt="$1"
+       local rt_neighs="$2"
+       local nsname
+       local net_prefix
+       local devname
+       local neigh
+
+       eval nsname=\${$(get_rtname "${rt}")}
+
+       for neigh in ${rt_neighs}; do
+               devname="veth-rt-${rt}-${neigh}"
+
+               net_prefix="$(get_network_prefix "${rt}" "${neigh}")"
+
+               ip -netns "${nsname}" addr \
+                       add "${net_prefix}::${rt}/64" dev "${devname}" nodad
+
+               ip -netns "${nsname}" link set "${devname}" up
+       done
+
+       ip -netns "${nsname}" link add "${DUMMY_DEVNAME}" type dummy
+
+       ip -netns "${nsname}" link set "${DUMMY_DEVNAME}" up
+       ip -netns "${nsname}" link set lo up
+
+       ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0
+       ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0
+       ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=1
+       ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=1
+}
+
+setup_rt_local_sids()
+{
+       local rt="$1"
+       local rt_neighs="$2"
+       local net_prefix
+       local devname
+       local nsname
+       local neigh
+
+       eval nsname=\${$(get_rtname "${rt}")}
+
+       for neigh in ${rt_neighs}; do
+               devname="veth-rt-${rt}-${neigh}"
+
+               net_prefix="$(get_network_prefix "${rt}" "${neigh}")"
+
+               # set underlay network routes for SIDs reachability
+               ip -netns "${nsname}" -6 route \
+                       add "${VPN_LOCATOR_SERVICE}:${neigh}::/32" \
+                       table "${LOCALSID_TABLE_ID}" \
+                       via "${net_prefix}::${neigh}" dev "${devname}"
+       done
+
+       # Local End.DT2U behavior: decapsulate L2 frames and deliver on
+       # srl2-0 which is a bridge port; the bridge then forwards to the
+       # host connected via veth-hs.
+       ip -netns "${nsname}" -6 route \
+               add "${VPN_LOCATOR_SERVICE}:${rt}::${DT2U_FUNC}" \
+               table "${LOCALSID_TABLE_ID}" \
+               encap seg6local action End.DT2U l2dev "${SRL2_DEVNAME}" \
+               dev "${DUMMY_DEVNAME}"
+
+       # all SIDs for VPNs start with a common locator. Routes and SRv6
+       # Endpoint behaviors instances are grouped together in the 'localsid'
+       # table.
+       ip -netns "${nsname}" -6 rule add \
+               to "${VPN_LOCATOR_SERVICE}::/16" \
+               lookup "${LOCALSID_TABLE_ID}" prio 999
+}
+
+setup_hs()
+{
+       local hs="$1"
+       local rt="$2"
+       local hsname
+       local rtname
+
+       eval hsname=\${$(get_hsname "${hs}")}
+       eval rtname=\${$(get_rtname "${rt}")}
+
+       ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.all.accept_dad=0
+       ip netns exec "${hsname}" sysctl -wq net.ipv6.conf.default.accept_dad=0
+
+       ip -netns "${hsname}" link add "${HS_VETH_NAME}" type veth \
+               peer name "${RT2HS_DEVNAME}" netns "${rtname}"
+
+       ip -netns "${hsname}" addr add "${IPv6_HS_NETWORK}::${hs}/64" \
+               dev "${HS_VETH_NAME}" nodad
+       ip -netns "${hsname}" addr add "${IPv4_HS_NETWORK}.${hs}/24" \
+               dev "${HS_VETH_NAME}"
+
+       ip -netns "${hsname}" link set "${HS_VETH_NAME}" up
+       ip -netns "${hsname}" link set lo up
+
+       # veth-hs is a bridge port; IPs go on br0 (see setup_bridge)
+       ip -netns "${rtname}" link set "${RT2HS_DEVNAME}" up
+}
+
+# Create srl2 device, bridge, and wire them together.
+# The srl2 device encapsulates L2 frames in IPv6+SRH towards the
+# remote router's End.DT2U SID. The bridge connects the host (via
+# veth-hs) with the SRv6 tunnel (via srl2-0).
+# args:
+#  $1 - router id
+#  $2 - remote router id (for SID list)
+setup_bridge()
+{
+       local rt="$1"
+       local remote_rt="$2"
+       local nsname
+
+       eval nsname=\${$(get_rtname "${rt}")}
+
+       # create the srl2 device pointing to the remote End.DT2U SID
+       ip -netns "${nsname}" link add "${SRL2_DEVNAME}" type srl2 \
+               segs "${VPN_LOCATOR_SERVICE}:${remote_rt}::${DT2U_FUNC}"
+       ip -netns "${nsname}" link set "${SRL2_DEVNAME}" up
+
+       # create bridge and add ports
+       ip -netns "${nsname}" link add "${BRIDGE_DEVNAME}" type bridge
+       ip -netns "${nsname}" link set "${BRIDGE_DEVNAME}" up
+
+       ip -netns "${nsname}" link set "${RT2HS_DEVNAME}" master \
+               "${BRIDGE_DEVNAME}"
+       ip -netns "${nsname}" link set "${SRL2_DEVNAME}" master \
+               "${BRIDGE_DEVNAME}"
+
+       # IP addresses on br0 (gateway for the hosts)
+       ip -netns "${nsname}" addr add "${IPv6_HS_NETWORK}::254/64" \
+               dev "${BRIDGE_DEVNAME}" nodad
+       ip -netns "${nsname}" addr \
+               add "${IPv4_HS_NETWORK}.254/24" dev "${BRIDGE_DEVNAME}"
+}
+
+setup()
+{
+       local i
+
+       # create routers
+       ROUTERS="1 2"; readonly ROUTERS
+       for i in ${ROUTERS}; do
+               create_router "${i}"
+       done
+
+       # create hosts
+       HOSTS="1 2"; readonly HOSTS
+       for i in ${HOSTS}; do
+               create_host "${i}"
+       done
+
+       # set up the links for connecting routers
+       add_link_rt_pairs 1 "2"
+
+       # set up the basic connectivity of routers and routes required for
+       # reachability of SIDs.
+       setup_rt_networking 1 "2"
+       setup_rt_networking 2 "1"
+
+       # set up the hosts connected to routers
+       setup_hs 1 1
+       setup_hs 2 2
+
+       # set up srl2 devices and bridges on each router.
+       # rt-1's srl2-0 encapsulates towards rt-2's End.DT2U SID and
+       # vice versa.
+       setup_bridge 1 2
+       setup_bridge 2 1
+
+       # set up SRv6 Endpoints (i.e. SRv6 End.DT2U)
+       setup_rt_local_sids 1 "2"
+       setup_rt_local_sids 2 "1"
+
+       # testing environment was set up successfully
+       SETUP_ERR=0
+}
+
+check_rt_connectivity()
+{
+       local rtsrc="$1"
+       local rtdst="$2"
+       local prefix
+       local rtsrc_nsname
+
+       eval rtsrc_nsname=\${$(get_rtname "${rtsrc}")}
+
+       prefix="$(get_network_prefix "${rtsrc}" "${rtdst}")"
+
+       ip netns exec "${rtsrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+               "${prefix}::${rtdst}" >/dev/null 2>&1
+}
+
+check_and_log_rt_connectivity()
+{
+       local rtsrc="$1"
+       local rtdst="$2"
+
+       check_rt_connectivity "${rtsrc}" "${rtdst}"
+       log_test $? 0 "Routers connectivity: rt-${rtsrc} -> rt-${rtdst}"
+}
+
+check_hs_ipv6_connectivity()
+{
+       local hssrc="$1"
+       local hsdst="$2"
+       local hssrc_nsname
+
+       eval hssrc_nsname=\${$(get_hsname "${hssrc}")}
+
+       ip netns exec "${hssrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+               "${IPv6_HS_NETWORK}::${hsdst}" >/dev/null 2>&1
+}
+
+check_hs_ipv4_connectivity()
+{
+       local hssrc="$1"
+       local hsdst="$2"
+       local hssrc_nsname
+
+       eval hssrc_nsname=\${$(get_hsname "${hssrc}")}
+
+       ip netns exec "${hssrc_nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+               "${IPv4_HS_NETWORK}.${hsdst}" >/dev/null 2>&1
+}
+
+check_and_log_hs2gw_connectivity()
+{
+       local hssrc="$1"
+
+       check_hs_ipv6_connectivity "${hssrc}" 254
+       log_test $? 0 "IPv6 Hosts connectivity: hs-${hssrc} -> gw"
+
+       check_hs_ipv4_connectivity "${hssrc}" 254
+       log_test $? 0 "IPv4 Hosts connectivity: hs-${hssrc} -> gw"
+}
+
+check_and_log_hs_ipv6_connectivity()
+{
+       local hssrc="$1"
+       local hsdst="$2"
+
+       check_hs_ipv6_connectivity "${hssrc}" "${hsdst}"
+       log_test $? 0 "IPv6 Hosts connectivity: hs-${hssrc} -> hs-${hsdst}"
+}
+
+check_and_log_hs_ipv4_connectivity()
+{
+       local hssrc="$1"
+       local hsdst="$2"
+
+       check_hs_ipv4_connectivity "${hssrc}" "${hsdst}"
+       log_test $? 0 "IPv4 Hosts connectivity: hs-${hssrc} -> hs-${hsdst}"
+}
+
+check_and_log_hs_connectivity()
+{
+       local hssrc="$1"
+       local hsdst="$2"
+
+       check_and_log_hs_ipv4_connectivity "${hssrc}" "${hsdst}"
+       check_and_log_hs_ipv6_connectivity "${hssrc}" "${hsdst}"
+}
+
+router_tests()
+{
+       local i
+       local j
+
+       log_section "IPv6 routers connectivity test"
+
+       for i in ${ROUTERS}; do
+               for j in ${ROUTERS}; do
+                       if [ "${i}" -eq "${j}" ]; then
+                               continue
+                       fi
+
+                       check_and_log_rt_connectivity "${i}" "${j}"
+               done
+       done
+}
+
+host2gateway_tests()
+{
+       local hs
+
+       log_section "IPv4/IPv6 connectivity test among hosts and gateways"
+
+       for hs in ${HOSTS}; do
+               check_and_log_hs2gw_connectivity "${hs}"
+       done
+}
+
+host_vpn_tests()
+{
+       log_section "SRv6 srl2 + End.DT2U L2 VPN connectivity test hosts (h1 
<-> h2)"
+
+       check_and_log_hs_connectivity 1 2
+       check_and_log_hs_connectivity 2 1
+}
+
+test_dummy_dev_or_ksft_skip()
+{
+       local test_netns
+
+       test_netns="dummy-$(mktemp -u XXXXXXXX)"
+
+       if ! ip netns add "${test_netns}"; then
+               echo "SKIP: Cannot set up netns for testing dummy dev support"
+               exit "${ksft_skip}"
+       fi
+
+       modprobe dummy &>/dev/null || true
+       if ! ip -netns "${test_netns}" link \
+               add "${DUMMY_DEVNAME}" type dummy; then
+               echo "SKIP: dummy dev not supported"
+
+               ip netns del "${test_netns}"
+               exit "${ksft_skip}"
+       fi
+
+       ip netns del "${test_netns}"
+}
+
+test_srl2_dev_or_ksft_skip()
+{
+       local test_netns
+
+       test_netns="srl2-$(mktemp -u XXXXXXXX)"
+
+       if ! ip netns add "${test_netns}"; then
+               echo "SKIP: Cannot set up netns for testing srl2 dev support"
+               exit "${ksft_skip}"
+       fi
+
+       modprobe srl2 &>/dev/null || true
+       if ! ip -netns "${test_netns}" link \
+               add srl2-test type srl2 \
+               segs fc00::1; then
+               echo "SKIP: srl2 dev not supported"
+
+               ip netns del "${test_netns}"
+               exit "${ksft_skip}"
+       fi
+
+       ip netns del "${test_netns}"
+}
+
+test_iproute2_supp_or_ksft_skip()
+{
+       if ! ip route help 2>&1 | grep -qo "End.DT2U"; then
+               echo "SKIP: Missing SRv6 End.DT2U support in iproute2"
+               exit "${ksft_skip}"
+       fi
+
+       if ! ip link help srl2 2>&1 | grep -qo "srl2"; then
+               echo "SKIP: Missing srl2 link type support in iproute2"
+               exit "${ksft_skip}"
+       fi
+}
+
+if [ "$(id -u)" -ne 0 ]; then
+       echo "SKIP: Need root privileges"
+       exit "${ksft_skip}"
+fi
+
+# required programs to carry out this selftest
+test_command_or_ksft_skip ip
+test_command_or_ksft_skip ping
+test_command_or_ksft_skip sysctl
+test_command_or_ksft_skip grep
+
+test_iproute2_supp_or_ksft_skip
+test_dummy_dev_or_ksft_skip
+test_srl2_dev_or_ksft_skip
+
+set -e
+trap cleanup EXIT
+
+setup
+set +e
+
+router_tests
+host2gateway_tests
+host_vpn_tests
+
+print_log_test_results
-- 
2.20.1


Reply via email to