summaryrefslogtreecommitdiff
path: root/tools/testing/selftests
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xsk.c4
-rw-r--r--tools/testing/selftests/drivers/net/hw/Makefile1
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/csum.py2
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py296
-rw-r--r--tools/testing/selftests/drivers/net/hw/ncdevmem.c2
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/rss_ctx.py11
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/toeplitz.py22
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/tso.py2
-rw-r--r--tools/testing/selftests/drivers/net/lib/py/env.py27
-rwxr-xr-xtools/testing/selftests/drivers/net/xdp.py94
-rw-r--r--tools/testing/selftests/net/Makefile3
-rwxr-xr-xtools/testing/selftests/net/big_tcp_tunnels.sh188
-rw-r--r--tools/testing/selftests/net/config2
-rwxr-xr-xtools/testing/selftests/net/fou_mcast_encap.sh172
-rw-r--r--tools/testing/selftests/net/getsockopt_iter.c97
-rwxr-xr-xtools/testing/selftests/net/openvswitch/openvswitch.sh168
-rw-r--r--tools/testing/selftests/net/openvswitch/ovs-dpctl.py26
-rw-r--r--tools/testing/selftests/net/psock_snd.c140
-rwxr-xr-xtools/testing/selftests/net/psock_snd.sh10
-rwxr-xr-xtools/testing/selftests/net/rtnetlink.py101
-rwxr-xr-xtools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh1027
-rwxr-xr-xtools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh10
-rw-r--r--tools/testing/selftests/net/tcp_mmap.c10
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json164
24 files changed, 2516 insertions, 63 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
index 38ce6060b8fa..4549358cc8c2 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c
@@ -1164,8 +1164,8 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk,
bool test_timeout)
{
u32 i, idx = 0, valid_pkts = 0, valid_frags = 0, buffer_len;
+ struct xsk_umem_info *umem = ifobject->xsk_arr[0].umem_real;
struct pkt_stream *pkt_stream = xsk->pkt_stream;
- struct xsk_umem_info *umem = xsk->umem;
bool use_poll = ifobject->use_poll;
struct pollfd fds = { };
int ret;
@@ -1513,7 +1513,7 @@ static int thread_common_ops_tx(struct test_spec *test, struct ifobject *ifobjec
umem_tx->base_addr = 0;
umem_tx->next_buffer = 0;
- ret = xsk_configure(test, ifobject, umem_tx, true);
+ ret = xsk_configure(test, ifobject, umem_rx, true);
if (ret)
return ret;
ifobject->xsk = &ifobject->xsk_arr[0];
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index fd0535a96d84..234db5c2c90c 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -20,6 +20,7 @@ TEST_GEN_FILES := \
TEST_PROGS = \
csum.py \
devlink_port_split.py \
+ devlink_rate_cross_esw.py \
devlink_rate_tc_bw.py \
devmem.py \
ethtool.sh \
diff --git a/tools/testing/selftests/drivers/net/hw/csum.py b/tools/testing/selftests/drivers/net/hw/csum.py
index 3e3a89a34afe..0e99198f8d39 100755
--- a/tools/testing/selftests/drivers/net/hw/csum.py
+++ b/tools/testing/selftests/drivers/net/hw/csum.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-"""Run the tools/testing/selftests/net/csum testsuite."""
+"""Run the tools/testing/selftests/net/lib/csum testsuite."""
from os import path
diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py
new file mode 100755
index 000000000000..4416f024cb76
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py
@@ -0,0 +1,296 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Devlink Rate Cross-eswitch Scheduling Test Suite
+==================================================
+
+Control-plane tests for cross-eswitch TX scheduling via devlink-rate.
+Validates that VFs from different PFs on the same chip can share
+rate groups using the cross-device parent-dev attribute.
+
+Preconditions:
+- NETIF points to a bond device with exactly two interfaces.
+- the interfaces must be two PFs from different devices sharing the same chip.
+- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG:
+ - devlink dev eswitch set $DEV1 mode switchdev
+ - devlink dev eswitch set $DEV2 mode switchdev
+ - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime
+ - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime
+- test cases will be skipped if:
+ - the number of interfaces in the bond device is != 2.
+ - the kernel doesn't support devlink rates.
+ - the devlink API doesn't support cross-device parents (ENODEV).
+ - cross-esw rate scheduling returns EOPNOTSUPP.
+"""
+
+import errno
+import glob
+import os
+import time
+
+from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit
+from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import NetDrvEnv, DevlinkFamily
+from lib.py import NlError
+from lib.py import cmd, defer, ip, tool
+
+
+# --- Discovery and setup ---
+
+
+def get_bond_slaves(bond_ifname):
+ """Returns sorted list of slave netdev names for a bond."""
+ pattern = f"/sys/class/net/{bond_ifname}/lower_*"
+ lowers = glob.glob(pattern)
+ if not lowers:
+ raise KsftSkipEx(f"No bond slaves for {bond_ifname}")
+ slaves = []
+ for path in sorted(lowers):
+ name = os.path.basename(path)
+ if name.startswith("lower_"):
+ name = name[len("lower_"):]
+ slaves.append(name)
+ return slaves
+
+
+def discover_pfs(cfg):
+ """Discovers both PFs from bond slaves."""
+ slaves = get_bond_slaves(cfg.ifname)
+ if len(slaves) != 2:
+ raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}")
+
+ pf0, pf1 = slaves[0], slaves[1]
+ ksft_pr(f"PF0: {pf0} PF1: {pf1}")
+ return pf0, pf1
+
+
+def get_pci_addr(ifname):
+ """Resolves PCI address for a network interface."""
+ return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device"))
+
+
+def get_vf_port_index(pf_pci):
+ """Finds devlink port-index for vf0 under pf_pci."""
+ ports = tool("devlink", "port show", json=True)["port"]
+ for port_name, props in ports.items():
+ if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0:
+ return int(port_name.split("/")[-1])
+ raise KsftSkipEx(f"VF port not found for {pf_pci}")
+
+
+def cleanup_esw(pf):
+ """Removes VFs if created by tests."""
+ cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False)
+
+
+def setup_esw(pf):
+ """Creates 1 VF on 'pf'."""
+ path = f"/sys/class/net/{pf}/device/sriov_numvfs"
+ cmd(f"echo 0 > {path}", shell=True)
+ cmd(f"echo 1 > {path}", shell=True)
+ defer(cleanup_esw, pf)
+ time.sleep(2)
+
+ vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net"
+ entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else []
+ if not entries:
+ raise KsftSkipEx(f"VF not found for {pf}")
+ ip(f"link set dev {entries[0]} up")
+
+ pf_pci = get_pci_addr(pf)
+ vf_idx = get_vf_port_index(pf_pci)
+ ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})")
+ return pf_pci, vf_idx
+
+
+# --- Rate operation helpers ---
+
+
+def rate_new(devnl, dev_pci, node_name, **kwargs):
+ """Creates rate node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ params.update(kwargs)
+ try:
+ devnl.rate_new(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_new not supported") from e
+ raise KsftFailEx("rate_new failed") from e
+
+
+def rate_get(devnl, dev_pci, node_name):
+ """Gets rate node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ return devnl.rate_get(params)
+
+
+def rate_get_leaf(devnl, dev_pci, port_index):
+ """Gets rate leaf (VF)."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ }
+ return devnl.rate_get(params)
+
+
+def rate_del(devnl, dev_pci, node_name):
+ """Deletes rate node."""
+ devnl.rate_del({
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ })
+
+
+def rate_set_leaf(devnl, dev_pci, port_index, **kwargs):
+ """Sets rate attributes on a leaf (VF)."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ }
+ params.update(kwargs)
+ try:
+ devnl.rate_set(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_set not supported") from e
+ raise KsftFailEx("rate_set failed") from e
+
+
+def rate_set_leaf_parent(devnl, dev_pci, port_index,
+ parent_name, parent_dev_pci=None):
+ """Sets a leaf's parent, optionally cross-esw."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "port-index": port_index,
+ "rate-parent-node-name": parent_name,
+ }
+ if parent_dev_pci:
+ params["parent-dev"] = {
+ "bus-name": "pci",
+ "dev-name": parent_dev_pci,
+ }
+ try:
+ devnl.rate_set(params)
+ except NlError as e:
+ if e.error == errno.EOPNOTSUPP:
+ raise KsftSkipEx("rate_set not supported") from e
+ if parent_dev_pci and e.error == errno.ENODEV:
+ raise KsftSkipEx("Cross-esw scheduling not supported") from e
+ raise KsftFailEx("rate_set failed") from e
+
+
+def rate_clear_leaf_parent(devnl, dev_pci, port_index):
+ """Clears a leaf's parent."""
+ rate_set_leaf_parent(devnl, dev_pci, port_index, "")
+
+
+def rate_set_node(devnl, dev_pci, node_name, **kwargs):
+ """Sets rate attributes on a node."""
+ params = {
+ "bus-name": "pci",
+ "dev-name": dev_pci,
+ "rate-node-name": node_name,
+ }
+ params.update(kwargs)
+ devnl.rate_set(params)
+
+
+# --- Test cases ---
+
+
+def test_same_esw_parent(cfg):
+ """Assigns PF0's VF to PF0's group (same esw baseline)."""
+ pf0, _ = discover_pfs(cfg)
+ pf0_pci, vf0_idx = setup_esw(pf0)
+
+ rate_new(cfg.devnl, pf0_pci, "group0")
+ defer(rate_del, cfg.devnl, pf0_pci, "group0")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0")
+ defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
+
+ ksft_pr("Same-esw parent assignment succeeded")
+
+
+def test_cross_esw_parent(cfg):
+ """Sets cross-esw parent, then clear it."""
+ pf0, pf1 = discover_pfs(cfg)
+ pf0_pci, _ = setup_esw(pf0)
+ pf1_pci, vf1_idx = setup_esw(pf1)
+
+ rate_new(cfg.devnl, pf0_pci, "group1")
+ defer(rate_del, cfg.devnl, pf0_pci, "group1")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
+ "group1", parent_dev_pci=pf0_pci)
+ defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
+
+ ksft_pr("Cross-esw parent set and clear succeeded")
+
+
+def test_tx_rates_on_cross_esw(cfg):
+ """Sets tx_max on group and tx_share on leaves in a cross-esw setup."""
+ pf0, pf1 = discover_pfs(cfg)
+ pf0_pci, vf0_idx = setup_esw(pf0)
+ pf1_pci, vf1_idx = setup_esw(pf1)
+
+ rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000})
+ defer(rate_del, cfg.devnl, pf0_pci, "group2")
+ ksft_pr("rate-new succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
+ "group2", parent_dev_pci=pf0_pci)
+ defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
+ ksft_pr("set parent cross-esw succeeded")
+
+ rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2")
+ defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
+ ksft_pr("set parent same esw succeeded")
+
+ rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000})
+ rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx)
+ ksft_eq(rate["rate-tx-share"], 1000000)
+ rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000})
+ rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx)
+ ksft_eq(rate["rate-tx-share"], 2000000)
+ rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000})
+ rate = rate_get(cfg.devnl, pf0_pci, "group2")
+ ksft_eq(rate["rate-tx-max"], 250000000)
+
+ ksft_pr("tx_max and tx_share set on cross-esw group")
+
+
+def main() -> None:
+ """Main function."""
+
+ with NetDrvEnv(__file__, nsim_test=False) as cfg:
+ cfg.devnl = DevlinkFamily()
+
+ ksft_run(
+ cases=[
+ test_same_esw_parent,
+ test_cross_esw_parent,
+ test_tx_rates_on_cross_esw,
+ ],
+ args=(cfg,),
+ )
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index d96e8a3b5a65..ffe1d5c1fa4e 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -150,7 +150,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
ctx->size = size;
- ctx->devfd = open("/dev/udmabuf", O_RDWR);
+ ctx->devfd = open("/dev/udmabuf", O_RDONLY);
if (ctx->devfd < 0) {
pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");
goto err_free_ctx;
diff --git a/tools/testing/selftests/drivers/net/hw/rss_ctx.py b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
index f36f76d6ca59..5b25fa89c629 100755
--- a/tools/testing/selftests/drivers/net/hw/rss_ctx.py
+++ b/tools/testing/selftests/drivers/net/hw/rss_ctx.py
@@ -651,9 +651,14 @@ def test_rss_context_overlap(cfg, other_ctx=0):
ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
# Test the main context
- cnts = _get_rx_cnts(cfg)
- GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
- cnts = _get_rx_cnts(cfg, prev=cnts)
+ attempts = 3
+ for attempt in range(attempts):
+ cnts = _get_rx_cnts(cfg)
+ GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
+ cnts = _get_rx_cnts(cfg, prev=cnts)
+ if sum(cnts[:2]) >= 7000 and sum(cnts[2:4]) >= 7000:
+ break
+ ksft_pr(f"Skewed queue distribution, attempt {attempt + 1}/{attempts}: " + str(cnts))
ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts))
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py
index cd7e080e6f84..571732198b93 100755
--- a/tools/testing/selftests/drivers/net/hw/toeplitz.py
+++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py
@@ -21,6 +21,8 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
ETH_RSS_HASH_TOP = 1
# Must match RPS_MAX_CPUS in toeplitz.c
RPS_MAX_CPUS = 16
+# Cap Rx queues so IRQ pinning leaves free CPUs in the RPS_MAX_CPUS range
+QUEUE_CAP = 8
def _check_rps_and_rfs_not_configured(cfg):
@@ -48,6 +50,25 @@ def _get_cpu_for_irq(irq):
return int(data)
+def _cap_queue_count(cfg):
+ ehdr = {"header": {"dev-index": cfg.ifindex}}
+ chans = cfg.ethnl.channels_get(ehdr)
+
+ config = {}
+ restore = {}
+ for key in ("combined-count", "rx-count"):
+ cur = chans.get(key, 0)
+ if cur > QUEUE_CAP:
+ config[key] = QUEUE_CAP
+ restore[key] = cur
+
+ if not config:
+ return
+
+ cfg.ethnl.channels_set(ehdr | config)
+ defer(cfg.ethnl.channels_set, ehdr | restore)
+
+
def _get_irq_cpus(cfg):
"""
Read the list of IRQs for the device Rx queues.
@@ -177,6 +198,7 @@ def test(cfg, proto_flag, ipver, grp):
]
if grp:
+ _cap_queue_count(cfg)
_check_rps_and_rfs_not_configured(cfg)
if grp == "rss":
irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])
diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py
index 802bb4868046..67f6c9ca9a64 100755
--- a/tools/testing/selftests/drivers/net/hw/tso.py
+++ b/tools/testing/selftests/drivers/net/hw/tso.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
-"""Run the tools/testing/selftests/net/csum testsuite."""
+"""A simple test for TSO."""
import fcntl
import socket
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index e4ab99b905b1..e4acf3d8333f 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -159,13 +159,7 @@ class NetDrvEpEnv(NetDrvEnvBase):
self.remote = Remote(kind, args, src_path)
- self.addr_ipver = "6" if self.addr_v["6"] else "4"
- self.addr = self.addr_v[self.addr_ipver]
- self.remote_addr = self.remote_addr_v[self.addr_ipver]
-
- # Bracketed addresses, some commands need IPv6 to be inside []
- self.baddr = f"[{self.addr_v['6']}]" if self.addr_v["6"] else self.addr_v["4"]
- self.remote_baddr = f"[{self.remote_addr_v['6']}]" if self.remote_addr_v["6"] else self.remote_addr_v["4"]
+ self.set_ipver("6" if self.addr_v["6"] else "4")
self.ifname = self.dev['ifname']
self.ifindex = self.dev['ifindex']
@@ -252,6 +246,25 @@ class NetDrvEpEnv(NetDrvEnvBase):
if not self.addr_v[ipver] or not self.remote_addr_v[ipver]:
raise KsftSkipEx(f"Test requires IPv{ipver} connectivity")
+ def set_ipver(self, ipver):
+ """
+ Modify the IP version used by the generic address fields.
+ """
+ if ipver == getattr(self, "addr_ipver", None):
+ return
+
+ self.require_ipver(ipver)
+
+ self.addr_ipver = ipver
+ self.addr = self.addr_v[ipver]
+ self.remote_addr = self.remote_addr_v[ipver]
+
+ # Bracketed addresses, some commands need IPv6 to be inside []
+ self.baddr = (f"[{self.addr_v['6']}]" if ipver == "6"
+ else self.addr_v["4"])
+ self.remote_baddr = (f"[{self.remote_addr_v['6']}]" if ipver == "6"
+ else self.remote_addr_v["4"])
+
def require_nsim(self, nsim_test=True):
"""Require or exclude netdevsim for this test"""
if nsim_test and self._ns is None:
diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/selftests/drivers/net/xdp.py
index 2ad5932299e8..0369929f3c51 100755
--- a/tools/testing/selftests/drivers/net/xdp.py
+++ b/tools/testing/selftests/drivers/net/xdp.py
@@ -172,25 +172,45 @@ def _test_pass(cfg, bpf_info, msg_sz):
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch")
-def test_xdp_native_pass_sb(cfg):
+_ipvers = [
+ KsftNamedVariant("ipv4", "4"),
+ KsftNamedVariant("ipv6", "6"),
+]
+
+
+def _set_ipver_defer_restore(cfg, ipver):
+ old_ipver = cfg.addr_ipver
+ cfg.set_ipver(ipver)
+ defer(cfg.set_ipver, old_ipver)
+
+
+@ksft_variants(_ipvers)
+def test_xdp_native_pass_sb(cfg, ipver):
"""
Tests the XDP_PASS action for single buffer case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
_test_pass(cfg, bpf_info, 256)
-def test_xdp_native_pass_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_pass_mb(cfg, ipver):
"""
Tests the XDP_PASS action for a multi-buff size.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
_test_pass(cfg, bpf_info, 8000)
@@ -219,25 +239,33 @@ def _test_drop(cfg, bpf_info, msg_sz):
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch")
-def test_xdp_native_drop_sb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_drop_sb(cfg, ipver):
"""
Tests the XDP_DROP action for a signle-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
_test_drop(cfg, bpf_info, 256)
-def test_xdp_native_drop_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_drop_mb(cfg, ipver):
"""
Tests the XDP_DROP action for a multi-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
_test_drop(cfg, bpf_info, 8000)
@@ -287,13 +315,17 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens):
ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch")
-def test_xdp_native_tx_sb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_tx_sb(cfg, ipver):
"""
Tests the XDP_TX action for a single-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
# Ensure there's enough room for an ETH / IP / UDP header
@@ -302,13 +334,17 @@ def test_xdp_native_tx_sb(cfg):
_test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len])
-def test_xdp_native_tx_mb(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_tx_mb(cfg, ipver):
"""
Tests the XDP_TX action for a multi-buff case.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o",
"xdp.frags", 9000)
# The first packet ensures we exercise the fragmented code path.
@@ -447,13 +483,17 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst):
return {"status": "pass"}
-def test_xdp_native_adjst_tail_grow_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_tail_grow_data(cfg, ipver):
"""
Tests the XDP tail adjustment by growing packet data.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
offset_lst = [1, 16, 32, 64, 128, 256]
res = _test_xdp_native_tail_adjst(
@@ -465,13 +505,17 @@ def test_xdp_native_adjst_tail_grow_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-def test_xdp_native_adjst_tail_shrnk_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_tail_shrnk_data(cfg, ipver):
"""
Tests the XDP tail adjustment by shrinking packet data.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
offset_lst = [-16, -32, -64, -128, -256]
res = _test_xdp_native_tail_adjst(
@@ -535,7 +579,7 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
# after we eat into it. We send large-enough packets, but if HDS
# is enabled head will only contain headers. Don't try to eat
# more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14)
- l2_cut_off = 28 if cfg.addr_ipver == 4 else 48
+ l2_cut_off = 28 if cfg.addr_ipver == "4" else 48
if pkt_sz > hds_thresh and offset > l2_cut_off:
ksft_pr(
f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and "
@@ -579,18 +623,22 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
return {"status": "pass"}
-def test_xdp_native_adjst_head_grow_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_head_grow_data(cfg, ipver):
"""
Tests the XDP headroom growth support.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
This function sets up the packet size and offset lists, then calls the
_test_xdp_native_head_adjst_mb function to perform the actual test. The
test is passed if the headroom is successfully extended for given packet
sizes and offsets.
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
# Negative values result in headroom shrinking, resulting in growing of payload
@@ -600,18 +648,22 @@ def test_xdp_native_adjst_head_grow_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-def test_xdp_native_adjst_head_shrnk_data(cfg):
+@ksft_variants(_ipvers)
+def test_xdp_native_adjst_head_shrnk_data(cfg, ipver):
"""
Tests the XDP headroom shrinking support.
Args:
cfg: Configuration object containing network settings.
+ ipver: IP version to use ("4" or "6").
This function sets up the packet size and offset lists, then calls the
_test_xdp_native_head_adjst_mb function to perform the actual test. The
test is passed if the headroom is successfully shrunk for given packet
sizes and offsets.
"""
+ _set_ipver_defer_restore(cfg, ipver)
+
pkt_sz_lst = [512, 1024, 2048]
# Positive values result in headroom growing, resulting in shrinking of payload
@@ -621,12 +673,19 @@ def test_xdp_native_adjst_head_shrnk_data(cfg):
_validate_res(res, offset_lst, pkt_sz_lst)
-@ksft_variants([
- KsftNamedVariant("pass", XDPAction.PASS),
- KsftNamedVariant("drop", XDPAction.DROP),
- KsftNamedVariant("tx", XDPAction.TX),
-])
-def test_xdp_native_qstats(cfg, act):
+def _qstats_variants():
+ actions = [
+ ("pass", XDPAction.PASS),
+ ("drop", XDPAction.DROP),
+ ("tx", XDPAction.TX),
+ ]
+ for ipver in ["4", "6"]:
+ for name, act in actions:
+ yield KsftNamedVariant(f"{name}_ipv{ipver}", act, ipver)
+
+
+@ksft_variants(_qstats_variants())
+def test_xdp_native_qstats(cfg, act, ipver):
"""
Send 1000 messages. Expect XDP action specified in @act.
Make sure the packets were counted to interface level qstats
@@ -634,6 +693,7 @@ def test_xdp_native_qstats(cfg, act):
"""
cfg.require_cmd("socat")
+ _set_ipver_defer_restore(cfg, ipver)
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
prog_info = _load_xdp_prog(cfg, bpf_info)
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d..ab890e6f79dd 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -13,6 +13,7 @@ TEST_PROGS := \
arp_ndisc_untracked_subnets.sh \
bareudp.sh \
big_tcp.sh \
+ big_tcp_tunnels.sh \
bind_bhash.sh \
bpf_offload.py \
bridge_stp_mode.sh \
@@ -39,6 +40,7 @@ TEST_PROGS := \
fib_rule_tests.sh \
fib_tests.sh \
fin_ack_lat.sh \
+ fou_mcast_encap.sh \
fq_band_pktlimit.sh \
gre_gso.sh \
gre_ipv6_lladdr.sh \
@@ -85,6 +87,7 @@ TEST_PROGS := \
rxtimestamp.sh \
sctp_vrf.sh \
skf_net_off.sh \
+ srv6_encap_lookup_l3vpn_test.sh \
srv6_end_dt46_l3vpn_test.sh \
srv6_end_dt4_l3vpn_test.sh \
srv6_end_dt6_l3vpn_test.sh \
diff --git a/tools/testing/selftests/net/big_tcp_tunnels.sh b/tools/testing/selftests/net/big_tcp_tunnels.sh
new file mode 100755
index 000000000000..d6513ed8d4e8
--- /dev/null
+++ b/tools/testing/selftests/net/big_tcp_tunnels.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Testing for IPv4 and IPv6 BIG TCP over VXLAN and GENEVE tunnels.
+
+SERVER_NS=$(mktemp -u server-XXXXXXXX)
+SERVER_IP4="192.168.1.1"
+SERVER_IP6="2001:db8::1:1"
+SERVER_IP4_TUN="192.168.2.1"
+SERVER_IP6_TUN="2001:db8::2:1"
+
+CLIENT_NS=$(mktemp -u client-XXXXXXXX)
+CLIENT_IP4="192.168.1.2"
+CLIENT_IP6="2001:db8::1:2"
+CLIENT_IP4_TUN="192.168.2.2"
+CLIENT_IP6_TUN="2001:db8::2:2"
+
+: "${PACKETS_THRESHOLD:=1000}"
+
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+setup() {
+ ip netns add "$SERVER_NS"
+ ip netns add "$CLIENT_NS"
+ ip -netns "$SERVER_NS" link add link1 type veth peer name link0 netns "$CLIENT_NS"
+
+ ip -netns "$CLIENT_NS" link set link0 up
+ ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4/24" dev link0
+ ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6/112" dev link0 nodad
+ ip -netns "$CLIENT_NS" link set link0 \
+ gso_max_size 196608 gso_ipv4_max_size 196608 \
+ gro_max_size 196608 gro_ipv4_max_size 196608
+ ip -netns "$SERVER_NS" link set link1 up
+ ip -netns "$SERVER_NS" addr replace "$SERVER_IP4/24" dev link1
+ ip -netns "$SERVER_NS" addr replace "$SERVER_IP6/112" dev link1 nodad
+ ip -netns "$SERVER_NS" link set link1 \
+ gso_max_size 196608 gso_ipv4_max_size 196608 \
+ gro_max_size 196608 gro_ipv4_max_size 196608
+
+ ip netns exec "$SERVER_NS" netserver >/dev/null
+}
+
+setup_tunnel() {
+ if [ "$2" = 4 ]; then
+ SERVER_IP="$SERVER_IP4"
+ CLIENT_IP="$CLIENT_IP4"
+ echo "Setting up ${1^^} over IPv4, veth tx csum offload $3"
+ else
+ SERVER_IP="$SERVER_IP6"
+ CLIENT_IP="$CLIENT_IP6"
+ echo "Setting up ${1^^} over IPv6, veth tx csum offload $3"
+ fi
+
+ if [ "$1" = vxlan ]; then
+ ip -netns "$CLIENT_NS" link add tun0 type vxlan \
+ id 5001 remote "$SERVER_IP" local "$CLIENT_IP" dev link0 dstport 4789
+ else
+ ip -netns "$CLIENT_NS" link add tun0 type geneve \
+ id 5001 remote "$SERVER_IP"
+ fi
+ ip -netns "$CLIENT_NS" link set tun0 up
+ ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP4_TUN/24" dev tun0
+ ip -netns "$CLIENT_NS" addr replace "$CLIENT_IP6_TUN/112" dev tun0 nodad
+ ip -netns "$CLIENT_NS" link set tun0 \
+ gso_max_size 196608 gso_ipv4_max_size 196608 \
+ gro_max_size 196608 gro_ipv4_max_size 196608
+ if [ "$1" = vxlan ]; then
+ ip -netns "$SERVER_NS" link add tun1 type vxlan \
+ id 5001 remote "$CLIENT_IP" local "$SERVER_IP" dev link1 dstport 4789
+ else
+ ip -netns "$SERVER_NS" link add tun1 type geneve \
+ id 5001 remote "$CLIENT_IP"
+ fi
+ ip -netns "$SERVER_NS" link set tun1 up
+ ip -netns "$SERVER_NS" addr replace "$SERVER_IP4_TUN/24" dev tun1
+ ip -netns "$SERVER_NS" addr replace "$SERVER_IP6_TUN/112" dev tun1 nodad
+ ip -netns "$SERVER_NS" link set tun1 \
+ gso_max_size 196608 gso_ipv4_max_size 196608 \
+ gro_max_size 196608 gro_ipv4_max_size 196608
+
+ ip netns exec "$CLIENT_NS" ethtool -K link0 tx-checksumming "$3" > /dev/null
+ ip netns exec "$SERVER_NS" ethtool -K link1 tx-checksumming "$3" > /dev/null
+}
+
+cleanup_tunnel() {
+ ip -netns "$CLIENT_NS" link del tun0
+ ip -netns "$SERVER_NS" link del tun1
+}
+
+cleanup() {
+ ip netns pids "$SERVER_NS" | xargs -r kill
+ ip netns pids "$CLIENT_NS" | xargs -r kill
+ ip netns del "$SERVER_NS"
+ ip netns del "$CLIENT_NS"
+ rm -rf "$WORKDIR"
+}
+
+do_test() {
+ # When tx csum offload is off, software GSO is performed before passing the
+ # packet to veth. Check BIG TCP packets inside the VXLAN tunnel to verify
+ # the software checksum path: if the checksum code is broken, these packets
+ # will be dropped.
+ if [ "$3" = on ]; then
+ CAPTURE_IFACE='link'
+ if [ "$1" = 4 ]; then
+ IPTABLES=iptables
+ else
+ IPTABLES=ip6tables
+ fi
+ else
+ CAPTURE_IFACE='tun'
+ if [ "$2" = 4 ]; then
+ IPTABLES=iptables
+ else
+ IPTABLES=ip6tables
+ fi
+ fi
+ if [ "$2" = 4 ]; then
+ IPTABLES_SACK=iptables
+ else
+ IPTABLES_SACK=ip6tables
+ fi
+
+ ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -I PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp"
+ ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -I OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp"
+ ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -I OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack"
+
+ if [ "$2" = 4 ]; then
+ SERVER_IP="$SERVER_IP4_TUN"
+ echo "Running IPv4 traffic in the tunnel"
+ else
+ SERVER_IP="$SERVER_IP6_TUN"
+ echo "Running IPv6 traffic in the tunnel"
+ fi
+
+ ip netns exec "$CLIENT_NS" netperf -t TCP_STREAM -l 5 -H "$SERVER_IP" -- \
+ -m 80000 > /dev/null
+
+ PACKETS_SERVER=$(ip netns exec "$SERVER_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}')
+ PACKETS_CLIENT=$(ip netns exec "$CLIENT_NS" "$IPTABLES-save" -c -t raw | sed -rn '/ --comment bigtcp/{s/^\[([0-9]+):.*/\1/p;q}')
+ PACKETS_SACK=$(ip netns exec "$SERVER_NS" "$IPTABLES_SACK-save" -c -t raw | sed -rn '/ --comment sack/{s/^\[([0-9]+):.*/\1/p;q}')
+ ip netns exec "$SERVER_NS" "$IPTABLES" -w -t raw -D PREROUTING -i "${CAPTURE_IFACE}1" -m length ! --length 0:65535 -m comment --comment "bigtcp"
+ ip netns exec "$CLIENT_NS" "$IPTABLES" -w -t raw -D OUTPUT -o "${CAPTURE_IFACE}0" -m length ! --length 0:65535 -m comment --comment "bigtcp"
+ ip netns exec "$SERVER_NS" "$IPTABLES_SACK" -w -t raw -D OUTPUT -o "tun1" -p tcp -m tcp --tcp-flags ACK ACK --tcp-option 5 -m comment --comment "sack"
+
+ echo "Captured BIG TCP RX packets: $PACKETS_SERVER"
+ echo "Captured BIG TCP TX packets: $PACKETS_CLIENT"
+ echo "Captured TCP SACK packets: $PACKETS_SACK"
+ [ "$PACKETS_SERVER" -gt "$PACKETS_THRESHOLD" ] || return 1
+ [ "$PACKETS_CLIENT" -gt "$PACKETS_THRESHOLD" ] || return 1
+ [ "$PACKETS_SACK" -lt "$(( PACKETS_CLIENT / 2 ))" ] || return 1
+}
+
+if ! netperf -V &> /dev/null; then
+ echo "SKIP: Could not run test without netperf tool"
+ exit "$ksft_skip"
+fi
+
+if ! iptables --version &> /dev/null; then
+ echo "SKIP: Could not run test without iptables tool"
+ exit "$ksft_skip"
+fi
+
+if ! ethtool --version &> /dev/null; then
+ echo "SKIP: Could not run test without ethtool tool"
+ exit "$ksft_skip"
+fi
+
+if ! ip link help 2>&1 | grep gso_ipv4_max_size &> /dev/null; then
+ echo "SKIP: Could not run test without gso/gro_ipv4_max_size supported in ip-link"
+ exit "$ksft_skip"
+fi
+
+WORKDIR=$(mktemp -d)
+trap cleanup EXIT
+setup
+for tunnel in vxlan geneve; do
+ for tun_family in 4 6; do
+ for traffic_family in 4 6; do
+ for csum_offload in on off; do
+ setup_tunnel "$tunnel" "$tun_family" "$csum_offload" || exit "$?"
+ do_test "$tun_family" "$traffic_family" "$csum_offload" || exit "$?"
+ cleanup_tunnel
+ done
+ done
+ done
+done
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index e1ce35c2abbe..96fffca6547c 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -38,6 +38,8 @@ CONFIG_IP_NF_TARGET_REJECT=m
CONFIG_IP_NF_TARGET_TTL=m
CONFIG_IP_SCTP=m
CONFIG_IPV6=y
+CONFIG_IPV6_FOU=m
+CONFIG_IPV6_FOU_TUNNEL=m
CONFIG_IPV6_GRE=m
CONFIG_IPV6_ILA=m
CONFIG_IPV6_IOAM6_LWTUNNEL=y
diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
new file mode 100755
index 000000000000..70210d39fba3
--- /dev/null
+++ b/tools/testing/selftests/net/fou_mcast_encap.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test that UDP encapsulation (FOU) correctly handles packet resubmit
+# when packets are delivered via the multicast UDP delivery path.
+#
+# When a FOU-encapsulated packet arrives with a multicast destination IP,
+# __udp4_lib_mcast_deliver() / __udp6_lib_mcast_deliver() must resubmit
+# it to the inner protocol handler (e.g., GRE) rather than consuming it.
+# This test verifies both IPv4 and IPv6 paths by creating a FOU/GRETAP
+# tunnel with a multicast remote address and sending ping through it.
+#
+# The early demux optimization can mask this issue by routing packets via
+# the unicast path (udp[6]_unicast_rcv_skb), so we disable it to force
+# packets through the multicast delivery function.
+
+source lib.sh
+
+NSENDER=""
+NRECV=""
+
+FOU_PORT4=4797
+FOU_PORT6=4798
+MCAST4=239.0.0.1
+MCAST6=ff0e::1
+
+TUN4_S=192.168.99.1
+TUN4_R=192.168.99.2
+TUN6_S=2001:db8:99::1
+TUN6_R=2001:db8:99::2
+
+cleanup() {
+ cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+setup_common() {
+ setup_ns NSENDER NRECV
+
+ # Create veth pair directly inside namespaces to avoid name
+ # collisions with devices in the root namespace.
+ ip link add veth_s netns "$NSENDER" type veth \
+ peer name veth_r netns "$NRECV"
+
+ ip -n "$NSENDER" link set veth_s up
+ ip -n "$NRECV" link set veth_r up
+
+ # Same sysctl controls early demux for both IPv4 and IPv6.
+ ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0
+}
+
+setup_ipv4() {
+ # IPv4 FOU (CONFIG_NET_FOU) is built in on kernels configured for
+ # these tests, so no module load is needed here.
+ ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
+ ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add "$MCAST4/32" dev veth_r autojoin
+
+ ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s
+ ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r
+
+ # Sender: GRETAP with FOU encap (no FOU listener needed on TX side)
+ ip -n "$NSENDER" link add eoudp4 type gretap \
+ remote "$MCAST4" local 10.0.0.1 \
+ encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \
+ key "$MCAST4"
+ ip -n "$NSENDER" link set eoudp4 up
+ ip -n "$NSENDER" addr add "$TUN4_S/24" dev eoudp4
+
+ # Receiver: FOU listener + GRETAP
+ ip netns exec "$NRECV" ip fou add port "$FOU_PORT4" ipproto 47
+ ip -n "$NRECV" link add eoudp4 type gretap \
+ remote "$MCAST4" local 10.0.0.2 \
+ encap fou encap-sport "$FOU_PORT4" encap-dport "$FOU_PORT4" \
+ key "$MCAST4"
+ ip -n "$NRECV" link set eoudp4 up
+ ip -n "$NRECV" addr add "$TUN4_R/24" dev eoudp4
+
+ # Static neigh on sender: ARP replies cannot traverse the
+ # unidirectional multicast tunnel.
+ local recv_mac
+ recv_mac=$(ip -n "$NRECV" link show eoudp4 | awk '/ether/{print $2}')
+ ip -n "$NSENDER" neigh add "$TUN4_R" lladdr "$recv_mac" dev eoudp4
+}
+
+setup_ipv6() {
+ # Skip cleanly if IPv6 or the fou6 module is not available.
+ [ -e /proc/sys/net/ipv6 ] || return "$ksft_skip"
+ modprobe -q fou6 || return "$ksft_skip"
+
+ ip -n "$NSENDER" addr add 2001:db8::1/64 dev veth_s nodad
+ ip -n "$NRECV" addr add 2001:db8::2/64 dev veth_r nodad
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add "$MCAST6/128" dev veth_r autojoin
+
+ ip -n "$NSENDER" -6 route add ff00::/8 dev veth_s
+ ip -n "$NRECV" -6 route add ff00::/8 dev veth_r
+
+ # Sender: ip6gretap with FOU encap
+ ip -n "$NSENDER" link add eoudp6 type ip6gretap \
+ remote "$MCAST6" local 2001:db8::1 \
+ encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \
+ key 42
+ ip -n "$NSENDER" link set eoudp6 up
+ ip -n "$NSENDER" addr add "$TUN6_S/64" dev eoudp6 nodad
+
+ # Receiver: FOU listener (IPv6) + ip6gretap
+ ip netns exec "$NRECV" ip fou add port "$FOU_PORT6" ipproto 47 -6
+ ip -n "$NRECV" link add eoudp6 type ip6gretap \
+ remote "$MCAST6" local 2001:db8::2 \
+ encap fou encap-sport "$FOU_PORT6" encap-dport "$FOU_PORT6" \
+ key 42
+ ip -n "$NRECV" link set eoudp6 up
+ ip -n "$NRECV" addr add "$TUN6_R/64" dev eoudp6 nodad
+
+ # Static neigh on sender: neighbor discovery cannot traverse the
+ # unidirectional multicast tunnel.
+ local recv_mac
+ recv_mac=$(ip -n "$NRECV" link show eoudp6 | awk '/ether/{print $2}')
+ ip -n "$NSENDER" neigh add "$TUN6_R" lladdr "$recv_mac" dev eoudp6
+}
+
+get_rx_packets() {
+ local dev="$1"
+
+ ip -n "$NRECV" -s link show "$dev" | awk '/RX:/{getline; print $2}'
+}
+
+run_ping_test() {
+ local family="$1"
+ local dev="$2"
+ local dst="$3"
+ local name="$4"
+ local count=100
+ local rx_before rx_after rx_delta
+
+ # Warmup: let any initial broadcast/ND traffic settle
+ ip netns exec "$NSENDER" ping "$family" -c 1 -W 1 "$dst" \
+ >/dev/null 2>&1
+ sleep 1
+
+ rx_before=$(get_rx_packets "$dev")
+ ip netns exec "$NSENDER" ping "$family" -i 0.01 -c $count -W 1 "$dst" \
+ >/dev/null 2>&1
+ sleep 1
+ rx_after=$(get_rx_packets "$dev")
+
+ rx_delta=$((rx_after - rx_before))
+
+ if [ "$rx_delta" -ge "$count" ]; then
+ RET=$ksft_pass
+ else
+ RET=$ksft_fail
+ fi
+ log_test "$name (received $rx_delta/$count)"
+}
+
+setup_common
+setup_ipv4
+run_ping_test -4 eoudp4 "$TUN4_R" "FOU/GRETAP IPv4 multicast encap resubmit"
+
+if setup_ipv6; then
+ run_ping_test -6 eoudp6 "$TUN6_R" "FOU/ip6gretap IPv6 multicast encap resubmit"
+else
+ log_test_skip "FOU/ip6gretap IPv6 multicast encap resubmit"
+fi
+
+exit "$EXIT_STATUS"
diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c
index 209569354d0e..fe5a5268bc34 100644
--- a/tools/testing/selftests/net/getsockopt_iter.c
+++ b/tools/testing/selftests/net/getsockopt_iter.c
@@ -11,6 +11,8 @@
* that always reports the required buffer length back via optlen,
* even when the user buffer is too small to receive any group bits.
* - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path.
+ * - raw: ICMP_FILTER covers a fixed-size struct payload that clamps
+ * the length down on a short buffer instead of failing.
*
* Author: Breno Leitao <leitao@debian.org>
*/
@@ -24,12 +26,20 @@
#include <linux/rtnetlink.h>
#include <linux/time_types.h>
#include <linux/vm_sockets.h>
+#include <linux/icmp.h>
+#include <netinet/in.h>
#include <sys/socket.h>
#include "kselftest_harness.h"
#ifndef AF_VSOCK
#define AF_VSOCK 40
#endif
+#ifndef SOL_RAW
+#define SOL_RAW 255
+#endif
+#ifndef ICMP_FILTER
+#define ICMP_FILTER 1
+#endif
/* ---------- netlink ---------- */
@@ -297,4 +307,91 @@ TEST_F(vsock, connect_timeout_old_exact)
ASSERT_EQ(sizeof(tv), optlen);
}
+/* ---------- raw (ipv4) ---------- */
+
+FIXTURE(raw)
+{
+ int fd;
+};
+
+FIXTURE_SETUP(raw)
+{
+ struct icmp_filter filt = { .data = 0xdeadbeef };
+
+ self->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (self->fd < 0)
+ SKIP(return, "SOCK_RAW/ICMP socket: %s", strerror(errno));
+
+ if (setsockopt(self->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof(filt)) < 0)
+ SKIP(return, "set ICMP_FILTER: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(raw)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(raw, icmpfilter_exact)
+{
+ struct icmp_filter filt = {};
+ socklen_t optlen = sizeof(filt);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
+ &filt, &optlen));
+ ASSERT_EQ(sizeof(filt), optlen);
+ ASSERT_EQ(0xdeadbeef, filt.data);
+}
+
+TEST_F(raw, icmpfilter_oversize_clamped)
+{
+ char buf[16] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
+ buf, &optlen));
+ ASSERT_EQ(sizeof(struct icmp_filter), optlen);
+}
+
+/* Unlike the int/u64 options above, ICMP_FILTER clamps the length down
+ * to the user buffer instead of returning EINVAL: a short buffer
+ * succeeds and reports the truncated length back via optlen.
+ */
+TEST_F(raw, icmpfilter_undersize_clamped)
+{
+ char buf[2] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
+ buf, &optlen));
+ ASSERT_EQ(sizeof(buf), optlen);
+}
+
+TEST_F(raw, icmpfilter_wrong_proto)
+{
+ struct icmp_filter filt;
+ socklen_t optlen = sizeof(filt);
+ int fd;
+
+ fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
+ if (fd < 0)
+ SKIP(return, "SOCK_RAW/UDP socket: %s", strerror(errno));
+
+ ASSERT_EQ(-1, getsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, &optlen));
+ ASSERT_EQ(EOPNOTSUPP, errno);
+ close(fd);
+}
+
+TEST_F(raw, bad_optname)
+{
+ socklen_t optlen;
+ int val;
+
+ optlen = sizeof(val);
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(val), optlen);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 2954245129a2..853dbc1b00d7 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -32,6 +32,8 @@ tests="
dec_ttl ttl: dec_ttl decrements IP TTL
flow_set flow-set: Flow modify
action_set set: SET action rewrites fields
+ trunc trunc: output truncation
+ icmpv6 icmpv6: ICMPv6 echo type match
psample psample: Sampling packets with psample"
info() {
@@ -443,6 +445,172 @@ test_action_set() {
return 0
}
+# trunc test
+# - trunc(14): truncate to ETH_HLEN, strips IP payload, ping fails
+# - trunc(1) and trunc(13): kernel rejects below ETH_HLEN (EINVAL)
+# - restore normal forwarding and verify recovery
+test_trunc() {
+ sbx_add "test_trunc" || return $?
+ ovs_add_dp "test_trunc" trunctest || return 1
+
+ info "create namespaces"
+ for ns in client server; do
+ ovs_add_netns_and_veths "test_trunc" "trunctest" \
+ "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1
+ done
+
+ ip netns exec client ip addr add 10.0.0.1/24 dev c1
+ ip netns exec client ip link set c1 up
+ ip netns exec server ip addr add 10.0.0.2/24 dev s1
+ ip netns exec server ip link set s1 up
+
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ '2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' \
+ '1' || return 1
+
+ info "verify connectivity without truncation"
+ ovs_sbx "test_trunc" ip netns exec client \
+ ping -c 1 -W 2 10.0.0.2 || return 1
+
+ # trunc below ETH_HLEN must be rejected by the kernel
+ info "verify trunc(1) is rejected"
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'trunc(1),2' &> /dev/null \
+ && { info "trunc(1) should be rejected"; return 1; }
+
+ info "verify trunc(13) is rejected"
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'trunc(13),2' &> /dev/null \
+ && { info "trunc(13) should be rejected"; return 1; }
+
+ ovs_del_flows "test_trunc" trunctest
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+
+ info "add trunc(14) forwarding flow"
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ 'trunc(14),2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' \
+ '1' || return 1
+
+ info "verify ping fails with trunc(14)"
+ ovs_sbx "test_trunc" ip netns exec client \
+ ping -c 1 -W 2 10.0.0.2 >/dev/null 2>&1 \
+ && { info "ping should fail with trunc(14)"
+ return 1; }
+
+ ovs_del_flows "test_trunc" trunctest
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0806),arp()' '2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0806),arp()' '1' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(1),eth(),eth_type(0x0800),ipv4()' \
+ '2' || return 1
+ ovs_add_flow "test_trunc" trunctest \
+ 'in_port(2),eth(),eth_type(0x0800),ipv4()' \
+ '1' || return 1
+
+ info "verify connectivity restored"
+ ovs_sbx "test_trunc" ip netns exec client \
+ ping -c 1 -W 2 10.0.0.2 || return 1
+
+ return 0
+}
+
+# icmpv6 test
+# - static neighbours to bypass NDP (nud permanent)
+# - icmpv6(type=128) echo request, icmpv6(type=129) echo reply
+# - remove flows and verify ping fails, reinstall and recover
+test_icmpv6() {
+ local t="test_icmpv6"
+ local v6="eth_type(0x86dd),ipv6(proto=58)"
+
+ sbx_add "$t" || return $?
+ ovs_add_dp "$t" icmpv6 || return 1
+
+ info "create namespaces"
+ for ns in client server; do
+ ovs_add_netns_and_veths "$t" "icmpv6" \
+ "$ns" "${ns:0:1}0" "${ns:0:1}1" || return 1
+ done
+
+ ip netns exec client ip addr add fd00::1/64 dev c1 nodad
+ ip netns exec client ip link set c1 up
+ ip netns exec server ip addr add fd00::2/64 dev s1 nodad
+ ip netns exec server ip link set s1 up
+
+ local cl_mac sl_mac
+ cl_mac=$(ip netns exec client ip link show c1 \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$cl_mac" ] && \
+ { info "failed to get c1 hwaddr"; return 1; }
+ sl_mac=$(ip netns exec server ip link show s1 \
+ | awk '/link\/ether/ {print $2}')
+ [ -z "$sl_mac" ] && \
+ { info "failed to get s1 hwaddr"; return 1; }
+ ip netns exec client ip -6 neigh add fd00::2 \
+ lladdr "$sl_mac" nud permanent dev c1 || return 1
+ ip netns exec server ip -6 neigh add fd00::1 \
+ lladdr "$cl_mac" nud permanent dev s1 || return 1
+
+ # Probe: check if kernel supports icmpv6 flow key.
+ ovs_add_flow "$t" icmpv6 \
+ "in_port(1),eth(),$v6,icmpv6(type=128)" \
+ '2' &>/dev/null
+ if [ $? -ne 0 ]; then
+ info "no support for icmpv6 key - skipping"
+ ovs_exit_sig
+ return $ksft_skip
+ fi
+ ovs_del_flows "$t" icmpv6
+
+ ovs_add_flow "$t" icmpv6 \
+ "in_port(1),eth(),$v6,icmpv6(type=128)" \
+ '2' || return 1
+ ovs_add_flow "$t" icmpv6 \
+ "in_port(2),eth(),$v6,icmpv6(type=129)" \
+ '1' || return 1
+
+ info "verify ICMPv6 echo with type-specific flows"
+ ovs_sbx "$t" ip netns exec client \
+ ping -6 -c 1 -W 2 fd00::2 || return 1
+
+ ovs_del_flows "$t" icmpv6
+
+ info "verify ping fails without echo flows"
+ ovs_sbx "$t" ip netns exec client \
+ ping -6 -c 1 -W 2 fd00::2 >/dev/null 2>&1 \
+ && { info "ping should fail without flows"
+ return 1; }
+
+ ovs_add_flow "$t" icmpv6 \
+ "in_port(1),eth(),$v6,icmpv6(type=128)" \
+ '2' || return 1
+ ovs_add_flow "$t" icmpv6 \
+ "in_port(2),eth(),$v6,icmpv6(type=129)" \
+ '1' || return 1
+
+ info "verify connectivity restored"
+ ovs_sbx "$t" ip netns exec client \
+ ping -6 -c 1 -W 2 fd00::2 || return 1
+
+ return 0
+}
+
# psample test
# - use psample to observe packets
test_psample() {
diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index e1ecfad2c03e..f3edd198223f 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -1255,11 +1255,16 @@ class ovskey(nla):
lambda x: ipaddress.IPv6Address(x).packed if x else 0,
convert_ipv6,
),
- ("label", "label", "%d", lambda x: int(x) if x else 0),
- ("proto", "proto", "%d", lambda x: int(x) if x else 0),
- ("tclass", "tclass", "%d", lambda x: int(x) if x else 0),
- ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0),
- ("frag", "frag", "%d", lambda x: int(x) if x else 0),
+ ("label", "label", "%d", lambda x: int(x) if x else 0,
+ convert_int(20)),
+ ("proto", "proto", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
+ ("tclass", "tclass", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
+ ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
+ ("frag", "frag", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
)
def __init__(
@@ -1344,8 +1349,10 @@ class ovskey(nla):
)
fields_map = (
- ("type", "type", "%d", lambda x: int(x) if x else 0),
- ("code", "code", "%d", lambda x: int(x) if x else 0),
+ ("type", "type", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
+ ("code", "code", "%d", lambda x: int(x) if x else 0,
+ convert_int(8)),
)
def __init__(
@@ -1983,6 +1990,11 @@ class ovskey(nla):
ovskey.ovs_key_icmp,
),
(
+ "OVS_KEY_ATTR_ICMPV6",
+ "icmpv6",
+ ovskey.ovs_key_icmpv6,
+ ),
+ (
"OVS_KEY_ATTR_TCP_FLAGS",
"tcp_flags",
lambda x: parse_flags(x, None),
diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c
index edf1e6f80d41..3313a15e0ca1 100644
--- a/tools/testing/selftests/net/psock_snd.c
+++ b/tools/testing/selftests/net/psock_snd.c
@@ -39,6 +39,8 @@ static bool cfg_use_gso;
static bool cfg_use_qdisc_bypass;
static bool cfg_use_vlan;
static bool cfg_use_vnet;
+static bool cfg_drop;
+static bool cfg_aux_data;
static char *cfg_ifname = "lo";
static int cfg_mtu = 1500;
@@ -49,6 +51,8 @@ static uint16_t cfg_port = 8000;
/* test sending up to max mtu + 1 */
#define TEST_SZ (sizeof(struct virtio_net_hdr) + ETH_HLEN + ETH_MAX_MTU + 1)
+#define BURST_CNT (1000)
+
static char tbuf[TEST_SZ], rbuf[TEST_SZ];
static unsigned long add_csum_hword(const uint16_t *start, int num_u16)
@@ -212,13 +216,14 @@ static void do_send(int fd, char *buf, int len)
if (ret != len)
error(1, 0, "write: %u %u", ret, len);
- fprintf(stderr, "tx: %u\n", ret);
+ if (!cfg_drop)
+ fprintf(stderr, "tx: %u\n", ret);
}
static int do_tx(void)
{
const int one = 1;
- int fd, len;
+ int i, fd, len;
fd = socket(PF_PACKET, cfg_use_dgram ? SOCK_DGRAM : SOCK_RAW, 0);
if (fd == -1)
@@ -242,6 +247,10 @@ static int do_tx(void)
do_send(fd, tbuf, len);
+ if (cfg_drop)
+ for (i = 0; i < BURST_CNT; i++)
+ do_send(fd, tbuf, len);
+
if (close(fd))
error(1, errno, "close t");
@@ -271,11 +280,54 @@ static int setup_rx(void)
return fd;
}
-static void do_rx(int fd, int expected_len, char *expected)
+static void check_aux_data(struct cmsghdr *cmsg, int expected_len)
+{
+ struct tpacket_auxdata *adata;
+
+ if (!cmsg)
+ error(1, 0, "auxdata null");
+
+ if (cmsg->cmsg_level != SOL_PACKET)
+ error(1, 0, "cmsg_level != SOL_PACKET");
+
+ if (cmsg->cmsg_type != PACKET_AUXDATA)
+ error(1, 0, "cmsg_type != PACKET_AUXDATA");
+
+ adata = (struct tpacket_auxdata *)CMSG_DATA(cmsg);
+
+ if (adata->tp_net != ETH_HLEN)
+ error(1, 0, "cmsg tp_net != ETH_HLEN");
+
+ if (adata->tp_len != expected_len)
+ error(1, 0, "cmsg tp_len != %u", expected_len);
+
+ if (adata->tp_snaplen != expected_len)
+ error(1, 0, "cmsg tp_snaplen != %u", expected_len);
+}
+
+static void do_rx(int fd, int expected_len, char *expected, bool is_psock)
{
+ char cmsg_buf[1024] __attribute__((aligned(8))) = {};
+ bool aux = is_psock && cfg_aux_data;
+ struct msghdr msg = {};
+ struct iovec iov[1];
int ret;
- ret = recv(fd, rbuf, sizeof(rbuf), 0);
+ if (aux) {
+ iov[0].iov_base = rbuf;
+ iov[0].iov_len = sizeof(rbuf);
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+
+ ret = recvmsg(fd, &msg, 0);
+ } else {
+ ret = recv(fd, rbuf, sizeof(rbuf), 0);
+ }
+
if (ret == -1)
error(1, errno, "recv");
if (ret != expected_len)
@@ -284,12 +336,19 @@ static void do_rx(int fd, int expected_len, char *expected)
if (memcmp(rbuf, expected, ret))
error(1, 0, "recv: data mismatch");
+ if (aux) {
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+ check_aux_data(cmsg, expected_len);
+ }
+
fprintf(stderr, "rx: %u\n", ret);
}
static int setup_sniffer(void)
{
struct timeval tv = { .tv_usec = 100 * 1000 };
+ const int one = 1;
int fd;
fd = socket(PF_PACKET, SOCK_RAW, 0);
@@ -299,6 +358,14 @@ static int setup_sniffer(void)
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
error(1, errno, "setsockopt rcv timeout");
+ if (cfg_drop)
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one)))
+ error(1, errno, "setsockopt SO_RCVBUF");
+
+ if (cfg_aux_data)
+ if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, &one, sizeof(one)))
+ error(1, errno, "setsockopt PACKET_AUXDATA");
+
pair_udp_setfilter(fd);
do_bind(fd);
@@ -309,8 +376,11 @@ static void parse_opts(int argc, char **argv)
{
int c;
- while ((c = getopt(argc, argv, "bcCdgl:qt:vV")) != -1) {
+ while ((c = getopt(argc, argv, "abcCdDgl:qt:vV")) != -1) {
switch (c) {
+ case 'a':
+ cfg_aux_data = true;
+ break;
case 'b':
cfg_use_bind = true;
break;
@@ -323,6 +393,9 @@ static void parse_opts(int argc, char **argv)
case 'd':
cfg_use_dgram = true;
break;
+ case 'D':
+ cfg_drop = true;
+ break;
case 'g':
cfg_use_gso = true;
break;
@@ -357,6 +430,49 @@ static void parse_opts(int argc, char **argv)
if (cfg_use_gso && !cfg_use_csum_off)
error(1, 0, "option gso (-g) requires csum offload (-c)");
+
+ if (cfg_aux_data && cfg_drop)
+ error(1, 0, "option aux data (-a) conflicts with drop (-D)");
+}
+
+static void check_packet_stats(int fd)
+{
+ struct tpacket_stats st = {};
+ socklen_t len = sizeof(st);
+
+ if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len))
+ error(1, errno, "getsockopt packet statistics");
+
+ if (cfg_drop) {
+ /* PACKET_STATISTICS reports all packets seen (including
+ * drops) in tp_packets
+ */
+ if (st.tp_packets < st.tp_drops)
+ error(1, 0, "stats: tp_packets %u < tp_drops %u",
+ st.tp_packets, st.tp_drops);
+
+ if (st.tp_drops == 0)
+ error(1, 0, "stats: expected drops but tp_drops == 0");
+ } else {
+ if (st.tp_packets != 1)
+ error(1, 0, "stats: tp_packets %u != 1", st.tp_packets);
+
+ if (st.tp_drops != 0)
+ error(1, 0, "stats: tp_drops %u != 0", st.tp_drops);
+ }
+
+ /* verify clear on read */
+ memset(&st, 0xff, sizeof(st));
+ len = sizeof(st);
+
+ if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len))
+ error(1, errno, "getsockopt packet statistics");
+
+ if (st.tp_packets != 0)
+ error(1, 0, "stats: tp_packets %u != 0 after clear", st.tp_packets);
+
+ if (st.tp_drops != 0)
+ error(1, 0, "stats: tp_drops %u != 0 after clear", st.tp_drops);
}
static void run_test(void)
@@ -368,13 +484,21 @@ static void run_test(void)
total_len = do_tx();
+ if (cfg_drop) {
+ check_packet_stats(fds);
+ goto out;
+ }
+
/* BPF filter accepts only this length, vlan changes MAC */
- if (cfg_payload_len == DATA_LEN && !cfg_use_vlan)
+ if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) {
do_rx(fds, total_len - sizeof(struct virtio_net_hdr),
- tbuf + sizeof(struct virtio_net_hdr));
+ tbuf + sizeof(struct virtio_net_hdr), true);
+ check_packet_stats(fds);
+ }
- do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len);
+ do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false);
+out:
if (close(fds))
error(1, errno, "close s");
if (close(fdr))
diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh
index 1cbfeb5052ec..111c9e2f0d21 100755
--- a/tools/testing/selftests/net/psock_snd.sh
+++ b/tools/testing/selftests/net/psock_snd.sh
@@ -92,4 +92,14 @@ echo "raw gso max size"
echo "raw gso max size + 1 (expected to fail)"
(! ./in_netns.sh ./psock_snd -v -c -g -l "${max_mss_exceeds}")
+# test drops statistics
+
+echo "test drops statistics"
+./in_netns.sh ./psock_snd -D
+
+# test aux data
+
+echo "test aux data"
+./in_netns.sh ./psock_snd -a
+
echo "OK. All tests passed"
diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py
index 3622413d793d..0c67c7c00d84 100755
--- a/tools/testing/selftests/net/rtnetlink.py
+++ b/tools/testing/selftests/net/rtnetlink.py
@@ -2,27 +2,106 @@
# SPDX-License-Identifier: GPL-2.0
import socket
+import struct
import time
-from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_ge, ksft_true, KsftSkipEx
+from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx
from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily
IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01'
+IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01'
+IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123')
+
+
+def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int):
+ """Return mc-users for grp on ifindex, or 0 if absent."""
+
+ addrs = rtnl.getmulticast({"ifa-family": family}, dump=True)
+ matches = [addr for addr in addrs
+ if addr['multicast'] == grp and addr['ifa-index'] == ifindex]
+ if not matches:
+ return 0
+ if 'mc-users' not in matches[0]:
+ return None
+
+ return matches[0]['mc-users']
+
def dump_mcaddr_check() -> None:
"""
- Verify that at least one interface has the IPv4 all-hosts multicast address.
- At least the loopback interface should have this address.
+ Verify IPv4 multicast addresses and their user counts in RTM_GETMULTICAST.
+ """
+
+ with NetNS() as ns:
+ with NetNSEnter(str(ns)):
+ ip("link set lo up")
+ rtnl = RtnlAddrFamily()
+ lo_idx = socket.if_nametoindex('lo')
+ addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True)
+
+ all_host_multicasts = [
+ addr for addr in addresses
+ if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST
+ ]
+
+ ksft_ge(len(all_host_multicasts), 1,
+ "No interface found with the IPv4 all-hosts multicast address")
+
+ mreq = IPV4_TEST_MULTICAST + socket.inet_aton('127.0.0.1')
+ before = _users_for(rtnl, socket.AF_INET, IPV4_TEST_MULTICAST, lo_idx)
+ if before is None:
+ raise KsftSkipEx("kernel does not expose IFA_MC_USERS")
+
+ s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ s1.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ s2.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+
+ after_join = _users_for(rtnl, socket.AF_INET,
+ IPV4_TEST_MULTICAST, lo_idx)
+ if after_join is None:
+ raise KsftSkipEx("kernel does not expose IFA_MC_USERS")
+ ksft_eq(after_join - before, 2,
+ f"users delta != 2 after two joins "
+ f"(before={before}, after={after_join})")
+ finally:
+ s1.close()
+ s2.close()
+
+
+def dump_mcaddr6_check() -> None:
+ """
+ Verify IPv6 multicast addresses and their user counts in RTM_GETMULTICAST.
"""
- rtnl = RtnlAddrFamily()
- addresses = rtnl.getmulticast({"ifa-family": socket.AF_INET}, dump=True)
+ with NetNS() as ns:
+ with NetNSEnter(str(ns)):
+ ip("link set lo up")
+ rtnl = RtnlAddrFamily()
+ lo_idx = socket.if_nametoindex('lo')
+ before = _users_for(rtnl, socket.AF_INET6,
+ IPV6_TEST_MULTICAST, lo_idx)
+ if before is None:
+ raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6")
+
+ mreq = IPV6_TEST_MULTICAST + struct.pack('=I', lo_idx)
+ s1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ s2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ try:
+ s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
+ s2.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
- all_host_multicasts = [
- addr for addr in addresses if addr['multicast'] == IPV4_ALL_HOSTS_MULTICAST
- ]
+ after_join = _users_for(rtnl, socket.AF_INET6,
+ IPV6_TEST_MULTICAST, lo_idx)
+ if after_join is None:
+ raise KsftSkipEx("kernel does not expose IFA_MC_USERS for IPv6")
+ ksft_eq(after_join - before, 2,
+ f"IPv6 users delta != 2 after two joins "
+ f"(before={before}, after={after_join})")
+ finally:
+ s1.close()
+ s2.close()
- ksft_ge(len(all_host_multicasts), 1,
- "No interface found with the IPv4 all-hosts multicast address")
def ipv4_devconf_notify() -> None:
"""
@@ -56,7 +135,7 @@ def ipv4_devconf_notify() -> None:
f"No 'forwarding on' notificiation found for interface {ifname}")
def main() -> None:
- ksft_run([dump_mcaddr_check, ipv4_devconf_notify])
+ ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify])
ksft_exit()
if __name__ == "__main__":
diff --git a/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh
new file mode 100755
index 000000000000..d6249303b7ea
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_encap_lookup_l3vpn_test.sh
@@ -0,0 +1,1027 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# author: Andrea Mayer <andrea.mayer@uniroma2.it>
+
+# This test evaluates the SRv6 encap "lookup" attribute. After encapsulation
+# the router looks up the route for the first SID, that is the outer IPv6
+# destination of the encapsulated packet. The attribute selects the FIB table
+# used for this post-encap SID route lookup.
+#
+# Two routers (rt-1, rt-2) provide L3 VPN services over an IPv6 underlay
+# (fd00::/64). Each router uses a separate VRF per tenant, with default
+# blackhole routes (IPv4 and IPv6) to prevent traffic from leaking out of
+# the VRF. Tenant traffic is encapsulated, then decapsulated with an
+# End.DT46. Each router proxies both NDP and ARP.
+#
+# The routes that match the first SIDs are installed in a dedicated underlay
+# table (500) rather than the main table (254). The encap routes use
+# "lookup 500" to select this table for the post-encap SID route lookup.
+#
+# Without the "lookup" attribute, the route for the first SID cannot be found:
+# - on the input path (forwarded traffic), the lookup stays in the VRF
+# and hits the blackhole;
+# - on the output path (locally originated traffic), the lookup falls
+# through to the main table, with no route to the first SID.
+#
+#
+# Legend (specific per-tenant addresses are in the instantiation tables below):
+# X = tenant id and VRF table id; two tenants: 100 and 200
+# ("tX" means tenant X, e.g. t100, t200)
+# a, b = the two host ids of the tenant
+# HA, HB = addresses of host a, host b
+# RLO1, RLO2 = rlo-X router loopback address on rt-1, rt-2 (tenant gateway
+# for the output path, dual-stack)
+# vrf-X = per-tenant VRF on each router (table X)
+#
+# Constants (same for every tenant):
+# underlay = table 500; post-encap SID route lookup (via "lookup 500")
+# localsid = table 90; holds the decap SIDs (End.DT46)
+# fd00::/64 = underlay link between rt-1 and rt-2
+# veth-tX = cafe::254/10.0.0.254 (tenant gateway on veth, both routers)
+#
+#
+# +-------------------+ +-------------------+
+# | | | |
+# | hs-tX-a netns | | hs-tX-b netns |
+# | | | |
+# | +-------------+ | | +-------------+ |
+# | | veth0 | | | | veth0 | |
+# | | HA | | | | HB | |
+# | +-------------+ | | +-------------+ |
+# | . | | . |
+# +-------------------+ +-------------------+
+# . .
+# . .
+# +-----------------------------------+ +-----------------------------------+
+# | . | | . |
+# | +---------------+ | | +---------------+ |
+# | | veth-tX | +----------+ | | +----------+ | veth-tX | |
+# | | ::254/.254 | | localsid | | | | localsid | | ::254/.254 | |
+# | +-------+-------+ +----------+ | | +----------+ +-------+-------+ |
+# | | +----------+ | | +----------+ | |
+# | +----+----+ | underlay | | | | underlay | +----+----+ |
+# | | vrf-X | +----------+ | | +----------+ | vrf-X | |
+# | +----+----+ | | +----+----+ |
+# | | | | | |
+# | +-----+----+ +------------+ | | +------------+ +----+-----+ |
+# | | rlo-X | | veth0 | | | | veth0 | | rlo-X | |
+# | | RLO1 | | fd00::1/64 |..|...|..| fd00::2/64 | | RLO2 | |
+# | +----------+ +------------+ | | +------------+ +----------+ |
+# | rt-1 netns | | rt-2 netns |
+# +-----------------------------------+ +-----------------------------------+
+#
+#
+# Per-tenant instantiation:
+# +-----+------+-------------------+-------------------+
+# | X | a, b | HA | HB |
+# +-----+------+-------------------+-------------------+
+# | 100 | 1, 2 | cafe::1, 10.0.0.1 | cafe::2, 10.0.0.2 |
+# | 200 | 3, 4 | cafe::3, 10.0.0.3 | cafe::4, 10.0.0.4 |
+# +-----+------+-------------------+-------------------+
+#
+# Router loopback (rlo-X) addresses, per tenant:
+# +-----+-----------------------+-----------------------+
+# | X | RLO1 (rt-1) | RLO2 (rt-2) |
+# +-----+-----------------------+-----------------------+
+# | 100 | cafe::101, 10.0.0.101 | cafe::102, 10.0.0.102 |
+# | 200 | cafe::201, 10.0.0.201 | cafe::202, 10.0.0.202 |
+# +-----+-----------------------+-----------------------+
+#
+#
+# Network configuration
+# =====================
+#
+# rt-1: localsid table (table 90)
+# +--------+--------------------+----------------------------------+
+# | tenant | SID | Action |
+# +--------+--------------------+----------------------------------+
+# | 100 | fc00:2:1:100::0d46 | apply SRv6 End.DT46 vrftable 100 |
+# | 200 | fc00:2:1:200::0d46 | apply SRv6 End.DT46 vrftable 200 |
+# +--------+--------------------+----------------------------------+
+#
+# rt-1: underlay table (table 500) - post-encap SID route lookup
+# +--------+--------------------+-------------------------------+
+# | tenant | SID | Action |
+# +--------+--------------------+-------------------------------+
+# | 100 | fc00:1:2:100::0d46 | forward via fd00::2 dev veth0 |
+# | 200 | fc00:1:2:200::0d46 | forward via fd00::2 dev veth0 |
+# +--------+--------------------+-------------------------------+
+#
+# rt-1: VRF tables (per tenant: vrf-X = table X)
+# +--------+------------+------------------------------------------+
+# | tenant | dst | encap action |
+# +--------+------------+------------------------------------------+
+# | 100 | cafe::2 | encap segs fc00:1:2:100::0d46 lookup 500 |
+# | | 10.0.0.2 | |
+# | | cafe::102 | |
+# | | 10.0.0.102 | |
+# | 200 | cafe::4 | encap segs fc00:1:2:200::0d46 lookup 500 |
+# | | 10.0.0.4 | |
+# | | cafe::202 | |
+# | | 10.0.0.202 | |
+# +--------+------------+------------------------------------------+
+#
+#
+# rt-2: localsid table (table 90)
+# +--------+--------------------+----------------------------------+
+# | tenant | SID | Action |
+# +--------+--------------------+----------------------------------+
+# | 100 | fc00:1:2:100::0d46 | apply SRv6 End.DT46 vrftable 100 |
+# | 200 | fc00:1:2:200::0d46 | apply SRv6 End.DT46 vrftable 200 |
+# +--------+--------------------+----------------------------------+
+#
+# rt-2: underlay table (table 500) - post-encap SID route lookup
+# +--------+--------------------+-------------------------------+
+# | tenant | SID | Action |
+# +--------+--------------------+-------------------------------+
+# | 100 | fc00:2:1:100::0d46 | forward via fd00::1 dev veth0 |
+# | 200 | fc00:2:1:200::0d46 | forward via fd00::1 dev veth0 |
+# +--------+--------------------+-------------------------------+
+#
+# rt-2: VRF tables (per tenant: vrf-X = table X)
+# +--------+------------+------------------------------------------+
+# | tenant | dst | encap action |
+# +--------+------------+------------------------------------------+
+# | 100 | cafe::1 | encap segs fc00:2:1:100::0d46 lookup 500 |
+# | | 10.0.0.1 | |
+# | | cafe::101 | |
+# | | 10.0.0.101 | |
+# | 200 | cafe::3 | encap segs fc00:2:1:200::0d46 lookup 500 |
+# | | 10.0.0.3 | |
+# | | cafe::201 | |
+# | | 10.0.0.201 | |
+# +--------+------------+------------------------------------------+
+# Within a tenant, a single SID reaches the adjacent router (its loopback)
+# and the remote host connected to it, in both IPv4 and IPv6.
+#
+# For both rt-1 and rt-2, each VRF also has the connected host prefix (cafe::/64
+# or 10.0.0.0/24) and a default blackhole (IPv4 and IPv6).
+#
+#
+# Locally originated traffic (output path)
+# ========================================
+#
+# The configuration above covers forwarded traffic, where packets arrive from
+# a host and are encapsulated by the router. To also test router-originated
+# traffic, each router pings the other router's loopback address through
+# the VPN.
+#
+# Example (tenant 100), rt-1 pings cafe::102 (rt-2's loopback):
+# 1. rt-1 looks up cafe::102 in vrf-100 and encapsulates it (SID
+# fc00:1:2:100::0d46), then "lookup 500" finds the route for the SID in the
+# underlay table (next hop fd00::2) and forwards it;
+# 2. rt-2 decapsulates it (localsid, End.DT46) and delivers it locally
+# (cafe::102 is on the rlo-100 interface);
+# 3. rt-2 replies with destination cafe::101 (rt-1's loopback). rt-2 looks up
+# cafe::101 in vrf-100 and encapsulates it back to rt-1 (again via "lookup
+# 500"). rt-1 decapsulates it and delivers it.
+
+# shellcheck source=lib.sh
+source lib.sh
+
+readonly LOCALSID_TABLE_ID=90
+readonly UNDERLAY_TABLE_ID=500
+readonly IPv6_RT_NETWORK=fd00
+readonly IPv6_HS_NETWORK=cafe
+readonly IPv4_HS_NETWORK=10.0.0
+readonly VPN_LOCATOR_SERVICE=fc00
+readonly DT46_FUNC=0d46
+readonly DUMMY_DEVNAME=dum0
+readonly IPv6_TESTS_ADDR=2001:db8::1
+readonly TESTS_TABLE_ID=54321
+PING_TIMEOUT_SEC=4
+
+SETUP_ERR=1
+
+ret=${ksft_skip}
+nsuccess=0
+nfail=0
+
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+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 -r 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 "################################################################################"
+}
+
+get_rtname()
+{
+ local rtid="$1"
+
+ echo "rt_${rtid}"
+}
+
+get_rt_nsname()
+{
+ local rtid="$1"
+ local varname
+
+ varname="$(get_rtname "${rtid}")"
+ echo "${!varname}"
+}
+
+get_hsname()
+{
+ local tid="$1"
+ local hsid="$2"
+
+ echo "hs_t${tid}_${hsid}"
+}
+
+get_hs_nsname()
+{
+ local tid="$1"
+ local hsid="$2"
+ local varname
+
+ varname="$(get_hsname "${tid}" "${hsid}")"
+ echo "${!varname}"
+}
+
+cleanup()
+{
+ ip link del veth-rt-1 2>/dev/null || true
+ ip link del veth-rt-2 2>/dev/null || true
+
+ cleanup_all_ns
+
+ # 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}"
+}
+
+# Host id of the router loopback (rlo) for a (router, tenant) pair.
+# E.g. rt-1/tenant 100 -> 101, rt-2/tenant 200 -> 202.
+get_rlo_hostid()
+{
+ local rtid="$1"
+ local tid="$2"
+
+ echo "$((tid + rtid))"
+}
+
+build_vpn_sid()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tid="$3"
+
+ echo "${VPN_LOCATOR_SERVICE}:${rtsrc}:${rtdst}:${tid}::${DT46_FUNC}"
+}
+
+# Install a dual-stack (IPv6 and IPv4) encap route in a VRF on the given
+# router.
+# args:
+# $1 - router id
+# $2 - host part of the IPv6 destination
+# $3 - host part of the IPv4 destination
+# $4 - SRv6 SID used as the encap destination
+# $5 - tenant id
+# $6 - if "true", add the "lookup" attribute to the encap route
+__set_encap_route()
+{
+ local rt="$1"
+ local dst6="$2"
+ local dst4="$3"
+ local sid="$4"
+ local tid="$5"
+ local use_lookup="$6"
+ local lookup=''
+ local rtname
+
+ rtname="$(get_rt_nsname "${rt}")"
+
+ if [ "${use_lookup}" = "true" ]; then
+ lookup="lookup ${UNDERLAY_TABLE_ID}"
+ fi
+
+ # shellcheck disable=SC2086
+ ip -netns "${rtname}" -6 route replace \
+ "${IPv6_HS_NETWORK}::${dst6}/128" vrf "vrf-${tid}" \
+ encap seg6 mode encap segs "${sid}" ${lookup} dev veth0
+
+ # shellcheck disable=SC2086
+ ip -netns "${rtname}" -4 route replace \
+ "${IPv4_HS_NETWORK}.${dst4}/32" vrf "vrf-${tid}" \
+ encap seg6 mode encap segs "${sid}" ${lookup} dev veth0
+}
+
+# Install the dual-stack encap route for a tenant host on rt, with the
+# "lookup" attribute so the first SID is looked up in the underlay table.
+# args:
+# $1 - router id where the encap route is installed
+# $2 - host destination id (host part of cafe::<id>/128 and 10.0.0.<id>/32)
+# $3 - SRv6 SID used as the encap destination
+# $4 - tenant id
+set_host_encap_route()
+{
+ local rt="$1"
+ local hsdst="$2"
+ local sid="$3"
+ local tid="$4"
+
+ __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" true
+}
+
+set_host_encap_route_nolookup()
+{
+ local rt="$1"
+ local hsdst="$2"
+ local sid="$3"
+ local tid="$4"
+
+ __set_encap_route "${rt}" "${hsdst}" "${hsdst}" "${sid}" "${tid}" false
+}
+
+# Install the dual-stack encap route on rtsrc toward rtdst's rlo loopback
+# (RLO1 or RLO2, see header), with the "lookup" attribute so the first
+# SID is looked up in the underlay table.
+# args:
+# $1 - router id where the encap route is installed
+# $2 - router id whose loopback address is the route destination
+# $3 - SRv6 SID used as the encap destination
+# $4 - tenant id
+set_gw_encap_route()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local sid="$3"
+ local tid="$4"
+ local dst
+
+ dst="$(get_rlo_hostid "${rtdst}" "${tid}")"
+
+ __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" true
+}
+
+set_gw_encap_route_nolookup()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local sid="$3"
+ local tid="$4"
+ local dst
+
+ dst="$(get_rlo_hostid "${rtdst}" "${tid}")"
+
+ __set_encap_route "${rtsrc}" "${dst}" "${dst}" "${sid}" "${tid}" false
+}
+
+# Setup the basic networking for a router
+setup_rt_networking()
+{
+ local id="$1"
+ local nsname
+
+ nsname="$(get_rt_nsname "${id}")"
+
+ ip link set "veth-rt-${id}" netns "${nsname}"
+ ip -netns "${nsname}" link set "veth-rt-${id}" name veth0
+
+ 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 "${nsname}" addr add "${IPv6_RT_NETWORK}::${id}/64" dev veth0 nodad
+ ip -netns "${nsname}" link set veth0 up
+
+ ip netns exec "${nsname}" sysctl -wq net.ipv4.ip_forward=1
+ ip netns exec "${nsname}" sysctl -wq net.ipv6.conf.all.forwarding=1
+}
+
+# Setup a host namespace and attach it to its gateway
+setup_hs()
+{
+ local hid="$1"
+ local rid="$2"
+ local tid="$3"
+ local rtveth="veth-t${tid}"
+ local hsname
+ local rtname
+
+ hsname="$(get_hs_nsname "${tid}" "${hid}")"
+ rtname="$(get_rt_nsname "${rid}")"
+
+ 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 "${rtveth}"
+ ip -netns "${hsname}" link set "${rtveth}" netns "${rtname}"
+
+ ip -netns "${hsname}" addr add \
+ "${IPv6_HS_NETWORK}::${hid}/64" dev veth0 nodad
+ ip -netns "${hsname}" addr add \
+ "${IPv4_HS_NETWORK}.${hid}/24" dev veth0
+
+ ip -netns "${hsname}" link set veth0 up
+}
+
+# Setup the per-tenant VRF on a router (gateway, loopback, blackhole)
+setup_rt()
+{
+ local rid="$1"
+ local tid="$2"
+ local rtveth="veth-t${tid}"
+ local rlo_dev="rlo-${tid}"
+ local rtname
+ local gw_addr_v6
+ local gw_addr_v4
+
+ rtname="$(get_rt_nsname "${rid}")"
+
+ gw_addr_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rid}" "${tid}")"
+ gw_addr_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rid}" "${tid}")"
+
+ ip -netns "${rtname}" link add "vrf-${tid}" type vrf table "${tid}"
+ ip -netns "${rtname}" link set "vrf-${tid}" up
+
+ ip -netns "${rtname}" link set "${rtveth}" master "vrf-${tid}"
+
+ ip -netns "${rtname}" addr add \
+ "${IPv6_HS_NETWORK}::254/64" dev "${rtveth}" nodad
+ ip -netns "${rtname}" addr add \
+ "${IPv4_HS_NETWORK}.254/24" dev "${rtveth}"
+
+ ip -netns "${rtname}" link set "${rtveth}" up
+
+ ip netns exec "${rtname}" \
+ sysctl -wq "net.ipv6.conf.${rtveth}.proxy_ndp=1"
+ ip netns exec "${rtname}" \
+ sysctl -wq "net.ipv4.conf.${rtveth}.proxy_arp=1"
+
+ ip netns exec "${rtname}" sh -c "echo 1 > /proc/sys/net/vrf/strict_mode"
+
+ # router loopback interface for locally originated traffic
+ ip -netns "${rtname}" link add "${rlo_dev}" type dummy
+ ip -netns "${rtname}" link set "${rlo_dev}" master "vrf-${tid}"
+
+ ip -netns "${rtname}" addr add "${gw_addr_v6}/128" \
+ dev "${rlo_dev}" nodad
+ ip -netns "${rtname}" addr add "${gw_addr_v4}/32" \
+ dev "${rlo_dev}"
+
+ ip -netns "${rtname}" link set "${rlo_dev}" up
+
+ # default blackhole routes in the VRF: any traffic that does not match
+ # a specific route is dropped. Without the "lookup" attribute on the
+ # encap route, the route for the first SID cannot be found from within
+ # the VRF.
+ ip -netns "${rtname}" -6 route add blackhole default metric 4278198272 \
+ vrf "vrf-${tid}"
+ ip -netns "${rtname}" -4 route add blackhole default metric 4278198272 \
+ vrf "vrf-${tid}"
+}
+
+# Configure a one-way VPN path towards hsdst (on rtdst) for tenant tid.
+# The encap side is set up on rtsrc and the decap side on rtdst.
+# args:
+# $1 - router id where the encap side is set up
+# $2 - host id of the destination host
+# $3 - router id of the destination router (connected to the destination host)
+# $4 - tenant id
+setup_vpn_config()
+{
+ local rtsrc="$1"
+ local hsdst="$2"
+ local rtdst="$3"
+ local tid="$4"
+ local rtveth="veth-t${tid}"
+ local rtsrc_name
+ local rtdst_name
+ local vpn_sid
+
+ rtsrc_name="$(get_rt_nsname "${rtsrc}")"
+ rtdst_name="$(get_rt_nsname "${rtdst}")"
+ vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")"
+
+ ip -netns "${rtsrc_name}" -6 neigh add proxy \
+ "${IPv6_HS_NETWORK}::${hsdst}" dev "${rtveth}"
+ set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}"
+
+ ip -netns "${rtsrc_name}" -6 route add "${vpn_sid}/128" \
+ table "${UNDERLAY_TABLE_ID}" \
+ via "fd00::${rtdst}" dev veth0
+
+ # set the decap route for decapsulating packets arriving from rtsrc
+ # and destined to hsdst
+ ip -netns "${rtdst_name}" -6 route add "${vpn_sid}/128" \
+ table "${LOCALSID_TABLE_ID}" \
+ encap seg6local action End.DT46 \
+ vrftable "${tid}" dev "vrf-${tid}"
+
+ # all SIDs for VPNs start with a common locator which is fc00::/16.
+ # Routes for handling the SRv6 End.DT* behavior instances are grouped
+ # together in the 'localsid' table.
+ #
+ # NOTE: added only once
+ if ! ip -netns "${rtdst_name}" -6 rule show | \
+ grep -q "to ${VPN_LOCATOR_SERVICE}::/16 lookup ${LOCALSID_TABLE_ID}"; then
+ ip -netns "${rtdst_name}" -6 rule add \
+ to "${VPN_LOCATOR_SERVICE}::/16" \
+ lookup "${LOCALSID_TABLE_ID}" prio 999
+ fi
+}
+
+# Configure rtsrc to reach rtdst's loopback address through the VPN.
+# args:
+# $1 - router id where the encap route is installed
+# $2 - router id whose loopback is the destination
+# $3 - tenant id
+setup_vpn_gw_encap()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tid="$3"
+ local sid
+
+ sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")"
+
+ set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}"
+}
+
+setup()
+{
+ ip link add veth-rt-1 type veth peer name veth-rt-2
+ setup_ns rt_1 rt_2
+ setup_rt_networking 1
+ setup_rt_networking 2
+
+ # setup two hosts for the tenant 100.
+ # - host hs-t100-1 is directly connected to the router rt-1;
+ # - host hs-t100-2 is directly connected to the router rt-2.
+ setup_ns hs_t100_1 hs_t100_2
+ setup_hs 1 1 100
+ setup_hs 2 2 100
+
+ # setup two hosts for the tenant 200.
+ # - host hs-t200-3 is directly connected to the router rt-1;
+ # - host hs-t200-4 is directly connected to the router rt-2.
+ setup_ns hs_t200_3 hs_t200_4
+ setup_hs 3 1 200
+ setup_hs 4 2 200
+
+ # configure each router for each tenant: VRF, blackhole routes,
+ # router loopback interface
+ setup_rt 1 100
+ setup_rt 2 100
+ setup_rt 1 200
+ setup_rt 2 200
+
+ # setup the L3 VPN which connects the host hs-t100-1 and host hs-t100-2
+ # within the same tenant 100.
+ setup_vpn_config 1 2 2 100
+ setup_vpn_config 2 1 1 100
+
+ # setup the L3 VPN which connects the host hs-t200-3 and host hs-t200-4
+ # within the same tenant 200.
+ setup_vpn_config 1 4 2 200
+ setup_vpn_config 2 3 1 200
+
+ # allow each router to reach the other's loopback through the VPN
+ setup_vpn_gw_encap 2 1 100
+ setup_vpn_gw_encap 1 2 100
+ setup_vpn_gw_encap 2 1 200
+ setup_vpn_gw_encap 1 2 200
+
+ # testing environment was set up successfully
+ SETUP_ERR=0
+}
+
+check_rt_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local nsname
+
+ nsname="$(get_rt_nsname "${rtsrc}")"
+
+ ip netns exec "${nsname}" ping -c 1 -W 1 "${IPv6_RT_NETWORK}::${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 tid="$3"
+ local nsname
+
+ nsname="$(get_hs_nsname "${tid}" "${hssrc}")"
+
+ ip netns exec "${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 tid="$3"
+ local nsname
+
+ nsname="$(get_hs_nsname "${tid}" "${hssrc}")"
+
+ ip netns exec "${nsname}" ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ "${IPv4_HS_NETWORK}.${hsdst}" >/dev/null 2>&1
+}
+
+check_and_log_hs_connectivity()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+ local tid="$3"
+
+ check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}"
+ log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})"
+
+ check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}"
+ log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> hs-t${tid}-${hsdst} (tenant ${tid})"
+}
+
+check_and_log_hs_isolation()
+{
+ local hssrc="$1"
+ local tidsrc="$2"
+ local hsdst="$3"
+ local tiddst="$4"
+
+ check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tidsrc}"
+ log_test $? 1 "IPv6 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}"
+
+ check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tidsrc}"
+ log_test $? 1 "IPv4 isolation: hs-t${tidsrc}-${hssrc} -X-> hs-t${tiddst}-${hsdst}"
+}
+
+check_and_log_hs2gw_connectivity()
+{
+ local hssrc="$1"
+ local tid="$2"
+
+ check_hs_ipv6_connectivity "${hssrc}" 254 "${tid}"
+ log_test $? 0 "IPv6 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})"
+
+ check_hs_ipv4_connectivity "${hssrc}" 254 "${tid}"
+ log_test $? 0 "IPv4 connectivity: hs-t${tid}-${hssrc} -> gw (tenant ${tid})"
+}
+
+router_tests()
+{
+ log_section "IPv6 routers connectivity test"
+
+ check_and_log_rt_connectivity 1 2
+ check_and_log_rt_connectivity 2 1
+}
+
+host2gateway_tests()
+{
+ log_section "Connectivity test among hosts and gateway"
+
+ check_and_log_hs2gw_connectivity 1 100
+ check_and_log_hs2gw_connectivity 2 100
+
+ check_and_log_hs2gw_connectivity 3 200
+ check_and_log_hs2gw_connectivity 4 200
+}
+
+host_vpn_tests()
+{
+ log_section "SRv6 VPN connectivity test among hosts in the same tenant"
+
+ check_and_log_hs_connectivity 1 2 100
+ check_and_log_hs_connectivity 2 1 100
+
+ check_and_log_hs_connectivity 3 4 200
+ check_and_log_hs_connectivity 4 3 200
+}
+
+host_vpn_isolation_tests()
+{
+ local l1="1 2"
+ local l2="3 4"
+ local t1=100
+ local t2=200
+ local i
+ local j
+ local tmp
+
+ log_section "SRv6 VPN isolation test among hosts in different tenants"
+
+ for _ in 0 1; do
+ for i in ${l1}; do
+ for j in ${l2}; do
+ check_and_log_hs_isolation "${i}" "${t1}" "${j}" "${t2}"
+ done
+ done
+
+ # let us test the reverse path
+ tmp="${l1}"; l1="${l2}"; l2="${tmp}"
+ tmp=${t1}; t1=${t2}; t2=${tmp}
+ done
+}
+
+__test_nolookup()
+{
+ local hssrc="$1"
+ local hsdst="$2"
+ local rtsrc="$3"
+ local rtdst="$4"
+ local tid="$5"
+ local vpn_sid
+
+ vpn_sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")"
+
+ # replace encap route(s) without "lookup" attribute
+ set_host_encap_route_nolookup "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}"
+
+ check_hs_ipv6_connectivity "${hssrc}" "${hsdst}" "${tid}"
+ log_test $? 1 "IPv6 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})"
+
+ check_hs_ipv4_connectivity "${hssrc}" "${hsdst}" "${tid}"
+ log_test $? 1 "IPv4 w/o lookup: hs-t${tid}-${hssrc} -X-> hs-t${tid}-${hsdst} (tenant ${tid})"
+
+ # restore encap route(s) with "lookup" for subsequent tests
+ set_host_encap_route "${rtsrc}" "${hsdst}" "${vpn_sid}" "${tid}"
+}
+
+host_vpn_nolookup_tests()
+{
+ log_section "SRv6 VPN connectivity test among hosts w/o lookup"
+
+ __test_nolookup 1 2 1 2 100
+ __test_nolookup 2 1 2 1 100
+
+ __test_nolookup 3 4 1 2 200
+ __test_nolookup 4 3 2 1 200
+}
+
+check_gw_ipv6_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tidsrc="$3"
+ local tiddst="$4"
+ local rtname
+ local src_v6
+ local dst_v6
+
+ rtname="$(get_rt_nsname "${rtsrc}")"
+ src_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtsrc}" "${tidsrc}")"
+ dst_v6="${IPv6_HS_NETWORK}::$(get_rlo_hostid "${rtdst}" "${tiddst}")"
+
+ ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \
+ ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ -I "${src_v6}" "${dst_v6}" >/dev/null 2>&1
+}
+
+check_gw_ipv4_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tidsrc="$3"
+ local tiddst="$4"
+ local rtname
+ local src_v4
+ local dst_v4
+
+ rtname="$(get_rt_nsname "${rtsrc}")"
+ src_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtsrc}" "${tidsrc}")"
+ dst_v4="${IPv4_HS_NETWORK}.$(get_rlo_hostid "${rtdst}" "${tiddst}")"
+
+ ip netns exec "${rtname}" ip vrf exec "vrf-${tidsrc}" \
+ ping -c 1 -W "${PING_TIMEOUT_SEC}" \
+ -I "${src_v4}" "${dst_v4}" >/dev/null 2>&1
+}
+
+check_and_log_gw_connectivity()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tid="$3"
+
+ check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}"
+ log_test $? 0 "IPv6 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})"
+
+ check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}"
+ log_test $? 0 "IPv4 connectivity: rt-${rtsrc} -> rt-${rtdst} (tenant ${tid})"
+}
+
+check_and_log_gw_isolation()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tidsrc="$3"
+ local tiddst="$4"
+
+ check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}"
+ log_test $? 1 "IPv6 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})"
+
+ check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tidsrc}" "${tiddst}"
+ log_test $? 1 "IPv4 isolation: rt-${rtsrc} -X-> rt-${rtdst} (tenants ${tidsrc}/${tiddst})"
+}
+
+gw_vpn_isolation_tests()
+{
+ log_section "SRv6 VPN isolation test among routers in different tenants"
+
+ check_and_log_gw_isolation 1 2 100 200
+ check_and_log_gw_isolation 2 1 100 200
+
+ check_and_log_gw_isolation 1 2 200 100
+ check_and_log_gw_isolation 2 1 200 100
+}
+
+gw_vpn_tests()
+{
+ log_section "SRv6 VPN connectivity test among routers in the same tenant"
+
+ check_and_log_gw_connectivity 1 2 100
+ check_and_log_gw_connectivity 2 1 100
+
+ check_and_log_gw_connectivity 1 2 200
+ check_and_log_gw_connectivity 2 1 200
+}
+
+__test_gw_nolookup()
+{
+ local rtsrc="$1"
+ local rtdst="$2"
+ local tid="$3"
+ local sid
+
+ sid="$(build_vpn_sid "${rtsrc}" "${rtdst}" "${tid}")"
+
+ # replace gw encap route without "lookup" attribute
+ set_gw_encap_route_nolookup "${rtsrc}" "${rtdst}" "${sid}" "${tid}"
+
+ check_gw_ipv6_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}"
+ log_test $? 1 "IPv6 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})"
+
+ check_gw_ipv4_connectivity "${rtsrc}" "${rtdst}" "${tid}" "${tid}"
+ log_test $? 1 "IPv4 w/o lookup: rt-${rtsrc} -X-> rt-${rtdst} (tenant ${tid})"
+
+ # restore gw encap route with "lookup" for subsequent tests
+ set_gw_encap_route "${rtsrc}" "${rtdst}" "${sid}" "${tid}"
+}
+
+gw_vpn_nolookup_tests()
+{
+ log_section "SRv6 VPN connectivity test among routers w/o lookup"
+
+ __test_gw_nolookup 1 2 100
+ __test_gw_nolookup 2 1 100
+
+ __test_gw_nolookup 1 2 200
+ __test_gw_nolookup 2 1 200
+}
+
+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
+}
+
+test_vrf_or_ksft_skip()
+{
+ modprobe vrf &>/dev/null || true
+ if [ ! -e /proc/sys/net/vrf/strict_mode ]; then
+ echo "SKIP: vrf sysctl does not exist"
+ exit "${ksft_skip}"
+ fi
+}
+
+test_dummy_dev_or_ksft_skip()
+{
+ local test_netns
+
+ setup_ns test_netns
+
+ modprobe dummy &>/dev/null || true
+ if ! ip -netns "${test_netns}" link add "${DUMMY_DEVNAME}" \
+ type dummy; then
+ cleanup_ns "${test_netns}"
+ echo "SKIP: dummy dev not supported"
+ exit "${ksft_skip}"
+ fi
+
+ cleanup_ns "${test_netns}"
+}
+
+test_encap_lookup_supp_or_ksft_skip()
+{
+ local nsname
+
+ setup_ns nsname
+
+ ip -netns "${nsname}" link add "${DUMMY_DEVNAME}" type dummy
+ ip -netns "${nsname}" link set "${DUMMY_DEVNAME}" up
+
+ if ! ip -netns "${nsname}" -6 route add "${IPv6_TESTS_ADDR}/128" \
+ encap seg6 mode encap segs fc00::1 \
+ lookup "${TESTS_TABLE_ID}" \
+ dev "${DUMMY_DEVNAME}" 2>/dev/null; then
+ cleanup_ns "${nsname}"
+ echo "SKIP: seg6 encap lookup attribute not supported"
+ exit "${ksft_skip}"
+ fi
+
+ # An old kernel with a recent iproute2 accepts the route but
+ # silently ignores the lookup attribute. Dump the route and check
+ # the attribute is really there, otherwise the test falsely passes.
+ if ! ip -netns "${nsname}" -6 route show "${IPv6_TESTS_ADDR}/128" | \
+ grep -q "lookup ${TESTS_TABLE_ID}"; then
+ cleanup_ns "${nsname}"
+ echo "SKIP: seg6 encap lookup attribute not supported"
+ exit "${ksft_skip}"
+ fi
+
+ cleanup_ns "${nsname}"
+}
+
+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_dummy_dev_or_ksft_skip
+test_vrf_or_ksft_skip
+test_encap_lookup_supp_or_ksft_skip
+
+set -e
+trap cleanup EXIT
+
+setup
+set +e
+
+router_tests
+host2gateway_tests
+host_vpn_tests
+host_vpn_isolation_tests
+host_vpn_nolookup_tests
+gw_vpn_tests
+gw_vpn_isolation_tests
+gw_vpn_nolookup_tests
+
+print_log_test_results
diff --git a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
index a5e959a080bb..50e37d3217ea 100755
--- a/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
+++ b/tools/testing/selftests/net/srv6_end_dt46_l3vpn_test.sh
@@ -536,6 +536,14 @@ host_vpn_isolation_tests()
done
}
+test_iproute2_supp_or_ksft_skip()
+{
+ if ! ip route add help 2>&1 | grep -qo "End.DT46"; then
+ echo "SKIP: Missing SRv6 End.DT46 support in iproute2"
+ exit "${ksft_skip}"
+ fi
+}
+
if [ "$(id -u)" -ne 0 ];then
echo "SKIP: Need root privileges"
exit $ksft_skip
@@ -546,6 +554,8 @@ if [ ! -x "$(command -v ip)" ]; then
exit $ksft_skip
fi
+test_iproute2_supp_or_ksft_skip
+
modprobe vrf &>/dev/null
if [ ! -e /proc/sys/net/vrf/strict_mode ]; then
echo "SKIP: vrf sysctl does not exist"
diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c
index 2544ae35d07a..487ae659a1f1 100644
--- a/tools/testing/selftests/net/tcp_mmap.c
+++ b/tools/testing/selftests/net/tcp_mmap.c
@@ -141,12 +141,12 @@ static void *mmap_large_buffer(size_t need, size_t *allocated)
buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
- if (buffer == (void *)-1) {
+ if (buffer == MAP_FAILED) {
sz = need;
buffer = mmap(NULL, sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE,
-1, 0);
- if (buffer != (void *)-1)
+ if (buffer != MAP_FAILED)
fprintf(stderr, "MAP_HUGETLB attempt failed, look at /sys/kernel/mm/hugepages for optimal performance\n");
}
*allocated = sz;
@@ -189,13 +189,13 @@ void *child_thread(void *arg)
fcntl(fd, F_SETFL, O_NDELAY);
buffer = mmap_large_buffer(chunk_size, &buffer_sz);
- if (buffer == (void *)-1) {
+ if (buffer == MAP_FAILED) {
perror("mmap");
goto error;
}
if (zflg) {
raddr = mmap(NULL, chunk_size + map_align, PROT_READ, flags, fd, 0);
- if (raddr == (void *)-1) {
+ if (raddr == MAP_FAILED) {
perror("mmap");
zflg = 0;
} else {
@@ -547,7 +547,7 @@ int main(int argc, char *argv[])
}
buffer = mmap_large_buffer(chunk_size, &buffer_sz);
- if (buffer == (unsigned char *)-1) {
+ if (buffer == MAP_FAILED) {
perror("mmap");
exit(1);
}
diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
index a1f97a4b606e..0cf12c50fb74 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
@@ -1540,5 +1540,169 @@
"$TC qdisc del dev $DUMMY root",
"$IP addr del 10.10.10.10/24 dev $DUMMY || true"
]
+ },
+ {
+ "id": "fb6c",
+ "name": "Force multiq to dequeue from its child's gso_skb with qfq leaf",
+ "category": [
+ "qdisc",
+ "tbf",
+ "multiq",
+ "qfq"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
+ "$IP link set dev $ETH up || true",
+ "$IP l set addr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP addr add 10.10.11.10/24 dev $ETH || true",
+ "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b",
+ "$TC qdisc add dev $ETH parent 1: handle 2: multiq",
+ "$TC qdisc add dev $ETH parent 2:1 handle 3: qfq",
+ "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1",
+ "$TC filter add dev $ETH parent 2: protocol all prio 1 matchall action skbedit queue_mapping 0",
+ "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok"
+ ],
+ "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j qdisc ls dev $ETH parent 1:",
+ "matchJSON": [
+ {
+ "kind": "multiq",
+ "handle": "2:",
+ "bytes": 98,
+ "packets": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $ETH handle 1: root",
+ "echo \"1\" > /sys/bus/netdevsim/del_device"
+ ]
+ },
+ {
+ "id": "1922",
+ "name": "Force multiq to dequeue from its child's gso_skb with dualpi2 leaf",
+ "category": [
+ "qdisc",
+ "tbf",
+ "multiq",
+ "dualpi2"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
+ "$IP link set dev $ETH up || true",
+ "$IP l set addr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP addr add 10.10.11.10/24 dev $ETH || true",
+ "$TC qdisc add dev $ETH root handle 1: tbf rate 88bit burst 1661b peakrate 2257333 minburst 1024 limit 7b",
+ "$TC qdisc add dev $ETH parent 1: handle 2: multiq",
+ "$TC qdisc add dev $ETH parent 2:1 handle 3: dualpi2",
+ "$TC filter add dev $ETH parent 2: protocol ip prio 1 u32 match ip dst 10.10.11.1 action skbedit queue_mapping 0",
+ "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok"
+ ],
+ "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:",
+ "matchJSON": [
+ {
+ "kind": "dualpi2",
+ "handle": "3:",
+ "bytes": 98,
+ "packets": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $ETH handle 1: root",
+ "echo \"1\" > /sys/bus/netdevsim/del_device"
+ ]
+ },
+ {
+ "id": "476f",
+ "name": "Force taprio to dequeue from its child's gso_skb with qfq leaf",
+ "category": [
+ "qdisc",
+ "tbf",
+ "multiq",
+ "qfq"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
+ "$IP link set dev $ETH up || true",
+ "$IP l set addr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true",
+ "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI",
+ "$TC qdisc add dev $ETH parent 1:1 handle 3: qfq",
+ "$TC class add dev $ETH classid 3:1 parent 3: qfq maxpkt 512 weight 1",
+ "$TC filter add dev $ETH parent 3: protocol all prio 1 matchall classid 3:1 action ok"
+ ],
+ "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j qdisc ls dev $ETH",
+ "matchJSON": [
+ {
+ "kind": "taprio",
+ "handle": "1:",
+ "bytes": 98,
+ "packets": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $ETH handle 1: root",
+ "echo \"1\" > /sys/bus/netdevsim/del_device"
+ ]
+ },
+ {
+ "id": "0235",
+ "name": "Force taprio to dequeue from its child's gso_skb with dualpi2 leaf",
+ "category": [
+ "qdisc",
+ "tbf",
+ "taprio",
+ "dualpi2"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device",
+ "$IP link set dev $ETH up || true",
+ "$IP l set addr 01:02:03:04:05:06 dev $ETH || true",
+ "$IP n add dev $ETH 10.10.11.1 lladdr 01:02:03:04:05:06 dev $ETH || true",
+ "$TC qdisc add dev $ETH root handle 1: taprio num_tc 2 map 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 queues 1@0 1@1 base-time 9000000000000000000 sched-entry S 03 200000 flags 0x0 clockid CLOCK_TAI",
+ "$TC qdisc replace dev $ETH parent 1:1 handle 3: dualpi2",
+ "$TC filter add dev $ETH parent 3: protocol ip prio 1 u32 match ip dst 10.10.11.1 classid 3:1 action ok"
+ ],
+ "cmdUnderTest": "ping -c 1 10.10.11.1 -W0.01 -I$ETH || true",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -j -s qdisc ls dev $ETH handle 3:",
+ "matchJSON": [
+ {
+ "kind": "dualpi2",
+ "handle": "3:",
+ "bytes": 98,
+ "packets": 1,
+ "backlog": 0,
+ "qlen": 0
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $ETH handle 1: root",
+ "echo \"1\" > /sys/bus/netdevsim/del_device"
+ ]
}
]