--- /dev/null
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Andrea Mayer <andrea.mayer@uniroma2.it>
+# author: Paolo Lungaroni <paolo.lungaroni@uniroma2.it>
+#
+# 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 := <X,   Y,   Z>):
+#  - 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 <id:op> as an input, the function returns
+# the <id> 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 <id:op> as an input, the function returns
+# the <op> 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 <nodeid:op>
+#       element constructs a SID that is associated with the behavior <op> on
+#       the <nodeid> 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