diff options
Diffstat (limited to 'tools/testing/selftests')
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" + ] } ] |
