#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # # author: Andrea Mayer # author: Paolo Lungaroni # # This script is designed to test the support for "flavors" in the SRv6 End # behavior. # # Flavors defined in RFC8986 [1] represent additional operations that can modify # or extend the existing SRv6 End, End.X and End.T behaviors. For the sake of # convenience, we report the list of flavors described in [1] hereafter: # - Penultimate Segment Pop (PSP); # - Ultimate Segment Pop (USP); # - Ultimate Segment Decapsulation (USD). # # The End, End.X, and End.T behaviors can support these flavors either # individually or in combinations. # Currently in this selftest we consider only the PSP flavor for the SRv6 End # behavior. However, it is possible to extend the script as soon as other # flavors will be supported in the kernel. # # The purpose of the PSP flavor consists in instructing the penultimate node # listed in the SRv6 policy to remove (i.e. pop) the outermost SRH from the IPv6 # header. # A PSP enabled SRv6 End behavior instance processes the SRH by: # - decrementing the Segment Left (SL) value from 1 to 0; # - copying the last SID from the SID List into the IPv6 Destination Address # (DA); # - removing the SRH from the extension headers following the IPv6 header. # # Once the SRH is removed, the IPv6 packet is forwarded to the destination using # the IPv6 DA updated during the PSP operation (i.e. the IPv6 DA corresponding # to the last SID carried by the removed SRH). # # Although the PSP flavor can be set for any SRv6 End behavior instance on any # SR node, it will be active only on such behaviors bound to a penultimate SID # for a given SRv6 policy. # SL=2 SL=1 SL=0 # | | | # For example, given the SRv6 policy (SID List := ): # - a PSP enabled SRv6 End behavior bound to SID Y will apply the PSP operation # as Segment Left (SL) is 1, corresponding to the Penultimate Segment of the # SID List; # - a PSP enabled SRv6 End behavior bound to SID X will *NOT* apply the PSP # operation as the Segment Left is 2. This behavior instance will apply the # "standard" End packet processing, ignoring the configured PSP flavor at # all. # # [1] RFC8986: https://datatracker.ietf.org/doc/html/rfc8986 # # Network topology # ================ # # The network topology used in this selftest is depicted hereafter, composed by # two hosts (hs-1, hs-2) and four routers (rt-1, rt-2, rt-3, rt-4). # Hosts hs-1 and hs-2 are connected to routers rt-1 and rt-2, respectively, # allowing them to communicate with each other. # Traffic exchanged between hs-1 and hs-2 can follow different network paths. # The network operator, through specific SRv6 Policies can steer traffic to one # path rather than another. In this selftest this is implemented as follows: # # i) The SRv6 H.Insert behavior applies SRv6 Policies on traffic received by # connected hosts. It pushes the Segment Routing Header (SRH) after the # IPv6 header. The SRH contains the SID List (i.e. SRv6 Policy) needed for # steering traffic across the segments/waypoints specified in that list; # # ii) The SRv6 End behavior advances the active SID in the SID List carried by # the SRH; # # iii) The PSP enabled SRv6 End behavior is used to remove the SRH when such # behavior is configured on a node bound to the Penultimate Segment carried # by the SID List. # # cafe::1 cafe::2 # +--------+ +--------+ # | | | | # | hs-1 | | hs-2 | # | | | | # +---+----+ +--- +---+ # cafe::/64 | | cafe::/64 # | | # +---+----+ +----+---+ # | | fcf0:0:1:2::/64 | | # | rt-1 +-------------------+ rt-2 | # | | | | # +---+----+ +----+---+ # | . . | # | fcf0:0:1:3::/64 . | # | . . | # | . . | # fcf0:0:1:4::/64 | . | fcf0:0:2:3::/64 # | . . | # | . . | # | fcf0:0:2:4::/64 . | # | . . | # +---+----+ +----+---+ # | | | | # | rt-4 +-------------------+ rt-3 | # | | fcf0:0:3:4::/64 | | # +---+----+ +----+---+ # # 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, at least two SIDs are # configured in the Local SID table: # # Local SID table for SRv6 router rt-x # +---------------------------------------------------------------------+ # |fcff:x::e is associated with the SRv6 End behavior | # |fcff:x::ef1 is associated with the SRv6 End behavior with PSP flavor | # +---------------------------------------------------------------------+ # # The fcff::/16 prefix is reserved by the operator for the SIDs. Reachability of # SIDs is ensured by proper configuration of the IPv6 operator's network and # SRv6 routers. # # # SRv6 Policies # ============= # # An SRv6 ingress router applies different SRv6 Policies to the traffic received # from connected hosts on the basis of the destination addresses. # In case of SRv6 H.Insert behavior, the SRv6 Policy enforcement consists of # pushing the SRH (carrying a given SID List) after the existing IPv6 header. # Note that in the inserting mode, there is no encapsulation at all. # # Before applying an SRv6 Policy using the SRv6 H.Insert behavior # +------+---------+ # | IPv6 | Payload | # +------+---------+ # # After applying an SRv6 Policy using the SRv6 H.Insert behavior # +------+-----+---------+ # | IPv6 | SRH | Payload | # +------+-----+---------+ # # Traffic from hs-1 to hs-2 # ------------------------- # # Packets generated from hs-1 and directed towards hs-2 are # handled by rt-1 which applies the following SRv6 Policy: # # i.a) IPv6 traffic, SID List=fcff:3::e,fcff:4::ef1,fcff:2::ef1,cafe::2 # # Router rt-1 is configured to enforce the Policy (i.a) through the SRv6 # H.Insert behavior which pushes the SRH after the existing IPv6 header. This # Policy steers the traffic from hs-1 across rt-3, rt-4, rt-2 and finally to the # destination hs-2. # # As the packet reaches the router rt-3, the SRv6 End behavior bound to SID # fcff:3::e is triggered. The behavior updates the Segment Left (from SL=3 to # SL=2) in the SRH, the IPv6 DA with fcff:4::ef1 and forwards the packet to the # next router on the path, i.e. rt-4. # # When router rt-4 receives the packet, the PSP enabled SRv6 End behavior bound # to SID fcff:4::ef1 is executed. Since the SL=2, the PSP operation is *NOT* # kicked in and the behavior applies the default End processing: the Segment # Left is decreased (from SL=2 to SL=1), the IPv6 DA is updated with the SID # fcff:2::ef1 and the packet is forwarded to router rt-2. # # The PSP enabled SRv6 End behavior on rt-2 is associated with SID fcff:2::ef1 # and is executed as the packet is received. Because SL=1, the behavior applies # the PSP processing on the packet as follows: i) SL is decreased, i.e. from # SL=1 to SL=0; ii) last SID (cafe::2) is copied into the IPv6 DA; iii) the # outermost SRH is removed from the extension headers following the IPv6 header. # Once the PSP processing is completed, the packet is forwarded to the host hs-2 # (destination). # # Traffic from hs-2 to hs-1 # ------------------------- # # Packets generated from hs-2 and directed to hs-1 are handled by rt-2 which # applies the following SRv6 Policy: # # i.b) IPv6 traffic, SID List=fcff:1::ef1,cafe::1 # # Router rt-2 is configured to enforce the Policy (i.b) through the SRv6 # H.Insert behavior which pushes the SRH after the existing IPv6 header. This # Policy steers the traffic from hs-2 across rt-1 and finally to the # destination hs-1 # # # When the router rt-1 receives the packet, the PSP enabled SRv6 End behavior # associated with the SID fcff:1::ef1 is triggered. Since the SL=1, # the PSP operation takes place: i) the SL is decremented; ii) the IPv6 DA is # set with the last SID; iii) the SRH is removed from the extension headers # after the IPv6 header. At this point, the packet with IPv6 DA=cafe::1 is sent # to the destination, i.e. hs-1. # Kselftest framework requirement - SKIP code is 4. readonly ksft_skip=4 readonly RDMSUFF="$(mktemp -u XXXXXXXX)" readonly DUMMY_DEVNAME="dum0" readonly RT2HS_DEVNAME="veth1" readonly LOCALSID_TABLE_ID=90 readonly IPv6_RT_NETWORK=fcf0:0 readonly IPv6_HS_NETWORK=cafe readonly IPv6_TESTS_ADDR=2001:db8::1 readonly LOCATOR_SERVICE=fcff readonly END_FUNC=000e readonly END_PSP_FUNC=0ef1 PING_TIMEOUT_SEC=4 PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} # IDs of routers and hosts are initialized during the setup of the testing # network 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}" # when a test fails, the value of 'ret' is set to 1 (error code). # Conversely, when all tests are passed successfully, the 'ret' value # is set to 0 (success code). 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_nodename() { local name="$1" echo "${name}-${RDMSUFF}" } get_rtname() { local rtid="$1" get_nodename "rt-${rtid}" } get_hsname() { local hsid="$1" get_nodename "hs-${hsid}" } __create_namespace() { local name="$1" ip netns add "${name}" } create_router() { local rtid="$1" local nsname nsname="$(get_rtname "${rtid}")" __create_namespace "${nsname}" } create_host() { local hsid="$1" local nsname nsname="$(get_hsname "${hsid}")" __create_namespace "${nsname}" } cleanup() { local nsname local i # destroy routers for i in ${ROUTERS}; do nsname="$(get_rtname "${i}")" ip netns del "${nsname}" &>/dev/null || true done # destroy hosts for i in ${HOSTS}; do nsname="$(get_hsname "${i}")" ip netns del "${nsname}" &>/dev/null || true done # check whether the setup phase was completed successfully or not. In # case of an error during the setup phase of the testing environment, # the selftest is considered as "skipped". 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 nsname="$(get_rtname "${rt}")" for neigh in ${rt_neighs}; do 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}" } # Given the description of a router as an input, the function returns # the token which represents the ID of the router. # i.e. input: "12:psp" # output: "12" __get_srv6_rtcfg_id() { local element="$1" echo "${element}" | cut -d':' -f1 } # Given the description of a router as an input, the function returns # the token which represents the operation (e.g. End behavior with or # withouth flavors) configured for the node. # Note that when the operation represents an End behavior with a list of # flavors, the output is the ordered version of that list. # i.e. input: "5:usp,psp,usd" # output: "psp,usd,usp" __get_srv6_rtcfg_op() { local element="$1" # return the lexicographically ordered flavors echo "${element}" | cut -d':' -f2 | sed 's/,/\n/g' | sort | \ xargs | sed 's/ /,/g' } # Setup the basic networking for the routers setup_rt_networking() { local rt="$1" local rt_neighs="$2" local nsname local net_prefix local devname local neigh 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 set lo up ip -netns "${nsname}" link add ${DUMMY_DEVNAME} type dummy ip -netns "${nsname}" link set ${DUMMY_DEVNAME} 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 } # Setup local SIDs for an SRv6 router setup_rt_local_sids() { local rt="$1" local rt_neighs="$2" local net_prefix local devname local nsname local neigh 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 "${LOCATOR_SERVICE}:${neigh}::/32" \ table "${LOCALSID_TABLE_ID}" \ via "${net_prefix}::${neigh}" dev "${devname}" done # Local End behavior (note that "dev" is a dummy interface chosen for # the sake of simplicity). ip -netns "${nsname}" -6 route \ add "${LOCATOR_SERVICE}:${rt}::${END_FUNC}" \ table "${LOCALSID_TABLE_ID}" \ encap seg6local action End dev "${DUMMY_DEVNAME}" # all SIDs start with a common locator. Routes and SRv6 Endpoint # behavior instaces are grouped together in the 'localsid' table. ip -netns "${nsname}" -6 rule \ add to "${LOCATOR_SERVICE}::/16" \ lookup "${LOCALSID_TABLE_ID}" prio 999 # set default routes to unreachable ip -netns "${nsname}" -6 route \ add unreachable default metric 4278198272 \ dev "${DUMMY_DEVNAME}" } # This helper function builds and installs the SID List (i.e. SRv6 Policy) # to be applied on incoming packets at the ingress node. Moreover, it # configures the SRv6 nodes specified in the SID List to process the traffic # according to the operations required by the Policy itself. # args: # $1 - destination host (i.e. cafe::x host) # $2 - SRv6 router configured for enforcing the SRv6 Policy # $3 - compact way to represent a list of SRv6 routers with their operations # (i.e. behaviors) that each of them needs to perform. Every # element constructs a SID that is associated with the behavior on # the node. The list of such elements forms an SRv6 Policy. __setup_rt_policy() { local dst="$1" local encap_rt="$2" local policy_rts="$3" local behavior_cfg local in_nsname local rt_nsname local policy='' local function local fullsid local op_type local node local n in_nsname="$(get_rtname "${encap_rt}")" for n in ${policy_rts}; do node="$(__get_srv6_rtcfg_id "${n}")" op_type="$(__get_srv6_rtcfg_op "${n}")" rt_nsname="$(get_rtname "${node}")" case "${op_type}" in "noflv") policy="${policy}${LOCATOR_SERVICE}:${node}::${END_FUNC}," function="${END_FUNC}" behavior_cfg="End" ;; "psp") policy="${policy}${LOCATOR_SERVICE}:${node}::${END_PSP_FUNC}," function="${END_PSP_FUNC}" behavior_cfg="End flavors psp" ;; *) break ;; esac fullsid="${LOCATOR_SERVICE}:${node}::${function}" # add SRv6 Endpoint behavior to the selected router if ! ip -netns "${rt_nsname}" -6 route get "${fullsid}" \ &>/dev/null; then ip -netns "${rt_nsname}" -6 route \ add "${fullsid}" \ table "${LOCALSID_TABLE_ID}" \ encap seg6local action ${behavior_cfg} \ dev "${DUMMY_DEVNAME}" fi done # we need to remove the trailing comma to avoid inserting an empty # address (::0) in the SID List. policy="${policy%,}" # add SRv6 policy to incoming traffic sent by connected hosts ip -netns "${in_nsname}" -6 route \ add "${IPv6_HS_NETWORK}::${dst}" \ encap seg6 mode inline segs "${policy}" \ dev "${DUMMY_DEVNAME}" ip -netns "${in_nsname}" -6 neigh \ add proxy "${IPv6_HS_NETWORK}::${dst}" \ dev "${RT2HS_DEVNAME}" } # see __setup_rt_policy setup_rt_policy_ipv6() { __setup_rt_policy "$1" "$2" "$3" } setup_hs() { local hs="$1" local rt="$2" local hsname local rtname hsname="$(get_hsname "${hs}")" 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 veth0 type veth \ peer name "${RT2HS_DEVNAME}" netns "${rtname}" ip -netns "${hsname}" addr \ add "${IPv6_HS_NETWORK}::${hs}/64" dev veth0 nodad ip -netns "${hsname}" link set veth0 up ip -netns "${hsname}" link set lo up ip -netns "${rtname}" addr \ add "${IPv6_HS_NETWORK}::254/64" dev "${RT2HS_DEVNAME}" nodad ip -netns "${rtname}" link set "${RT2HS_DEVNAME}" up ip netns exec "${rtname}" \ sysctl -wq net.ipv6.conf."${RT2HS_DEVNAME}".proxy_ndp=1 } setup() { local i # create routers ROUTERS="1 2 3 4"; 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 3 4" add_link_rt_pairs 2 "3 4" add_link_rt_pairs 3 "4" # set up the basic connectivity of routers and routes required for # reachability of SIDs. setup_rt_networking 1 "2 3 4" setup_rt_networking 2 "1 3 4" setup_rt_networking 3 "1 2 4" setup_rt_networking 4 "1 2 3" # set up the hosts connected to routers setup_hs 1 1 setup_hs 2 2 # set up default SRv6 Endpoints (i.e. SRv6 End behavior) setup_rt_local_sids 1 "2 3 4" setup_rt_local_sids 2 "1 3 4" setup_rt_local_sids 3 "1 2 4" setup_rt_local_sids 4 "1 2 3" # set up SRv6 policies # create a connection between hosts hs-1 and hs-2. # The path between hs-1 and hs-2 traverses SRv6 aware routers. # For each direction two path are chosen: # # Direction hs-1 -> hs-2 (PSP flavor) # - rt-1 (SRv6 H.Insert policy) # - rt-3 (SRv6 End behavior) # - rt-4 (SRv6 End flavor PSP with SL>1, acting as End behavior) # - rt-2 (SRv6 End flavor PSP with SL=1) # # Direction hs-2 -> hs-1 (PSP flavor) # - rt-2 (SRv6 H.Insert policy) # - rt-1 (SRv6 End flavor PSP with SL=1) setup_rt_policy_ipv6 2 1 "3:noflv 4:psp 2:psp" setup_rt_policy_ipv6 1 2 "1:psp" # testing environment was set up successfully SETUP_ERR=0 } check_rt_connectivity() { local rtsrc="$1" local rtdst="$2" local prefix local rtsrc_nsname 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 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_and_log_hs2gw_connectivity() { local hssrc="$1" check_hs_ipv6_connectivity "${hssrc}" 254 log_test $? 0 "IPv6 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_connectivity() { local hssrc="$1" local hsdst="$2" 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 "IPv6 connectivity test among hosts and gateways" for hs in ${HOSTS}; do check_and_log_hs2gw_connectivity "${hs}" done } host_srv6_end_flv_psp_tests() { log_section "SRv6 connectivity test hosts (h1 <-> h2, PSP flavor)" check_and_log_hs_connectivity 1 2 check_and_log_hs_connectivity 2 1 } test_iproute2_supp_or_ksft_skip() { local flavor="$1" if ! ip route help 2>&1 | grep -qo "${flavor}"; then echo "SKIP: Missing SRv6 ${flavor} flavor support in iproute2" exit "${ksft_skip}" fi } test_kernel_supp_or_ksft_skip() { local flavor="$1" local test_netns test_netns="kflv-$(mktemp -u XXXXXXXX)" if ! ip netns add "${test_netns}"; then echo "SKIP: Cannot set up netns to test kernel support for flavors" exit "${ksft_skip}" fi if ! ip -netns "${test_netns}" link \ add "${DUMMY_DEVNAME}" type dummy; then echo "SKIP: Cannot set up dummy dev to test kernel support for flavors" ip netns del "${test_netns}" exit "${ksft_skip}" fi if ! ip -netns "${test_netns}" link \ set "${DUMMY_DEVNAME}" up; then echo "SKIP: Cannot activate dummy dev to test kernel support for flavors" ip netns del "${test_netns}" exit "${ksft_skip}" fi if ! ip -netns "${test_netns}" -6 route \ add "${IPv6_TESTS_ADDR}" encap seg6local \ action End flavors "${flavor}" dev "${DUMMY_DEVNAME}"; then echo "SKIP: ${flavor} flavor not supported in kernel" ip netns del "${test_netns}" exit "${ksft_skip}" fi ip netns del "${test_netns}" } 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}" } 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_command_or_ksft_skip cut test_command_or_ksft_skip sed test_command_or_ksft_skip sort test_command_or_ksft_skip xargs test_dummy_dev_or_ksft_skip test_iproute2_supp_or_ksft_skip psp test_kernel_supp_or_ksft_skip psp set -e trap cleanup EXIT setup set +e router_tests host2gateway_tests host_srv6_end_flv_psp_tests print_log_test_results