diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 14:38:12 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 14:38:12 +0100 |
| commit | cf4d44c65ca6b17a91e38b11cb6a4cafb31961b4 (patch) | |
| tree | a026d391aee904c917eaaacb319149485af7b3a5 /tools | |
| parent | a4cddd6db711ad1564aff4298cdffe4e874810ea (diff) | |
| parent | 1704cc8640702c93e79921e2dffff3cfa31f0ce5 (diff) | |
| download | linux-next-cf4d44c65ca6b17a91e38b11cb6a4cafb31961b4.tar.gz linux-next-cf4d44c65ca6b17a91e38b11cb6a4cafb31961b4.zip | |
Merge branch 'main' of https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git
# Conflicts:
# MAINTAINERS
# tools/testing/selftests/net/lib.sh
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/net/ynl/pyynl/__init__.py | 9 | ||||
| -rwxr-xr-x | tools/net/ynl/pyynl/cli.py | 56 | ||||
| -rw-r--r-- | tools/net/ynl/pyynl/lib/__init__.py | 3 | ||||
| -rw-r--r-- | tools/net/ynl/pyynl/lib/nlspec.py | 22 | ||||
| -rw-r--r-- | tools/net/ynl/pyynl/lib/specdir.py | 51 | ||||
| -rw-r--r-- | tools/net/ynl/pyynl/lib/ynl.py | 19 | ||||
| -rwxr-xr-x | tools/net/ynl/tests/ethtool.py | 7 | ||||
| -rw-r--r-- | tools/testing/selftests/bpf/prog_tests/test_xsk.c | 4 | ||||
| -rwxr-xr-x | tools/testing/selftests/drivers/net/hw/toeplitz.py | 22 | ||||
| -rw-r--r-- | tools/testing/selftests/net/getsockopt_iter.c | 97 | ||||
| -rw-r--r-- | tools/testing/selftests/net/lib.sh | 15 | ||||
| -rwxr-xr-x | tools/testing/selftests/net/rtnetlink.py | 101 | ||||
| -rw-r--r-- | tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json | 164 |
13 files changed, 491 insertions, 79 deletions
diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py index e69de29bb2d1..d8f59c132ab7 100644 --- a/tools/net/ynl/pyynl/__init__.py +++ b/tools/net/ynl/pyynl/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" Python YNL (YAML Netlink) library. """ + +# Re-export the public library API so it can be imported straight from the +# package, e.g. `from pyynl import YnlFamily`. +# pylint: disable=wildcard-import,unused-wildcard-import +from .lib import * +from .lib import __all__ diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py index 8275a806cf73..b6a6ce12b4a7 100755 --- a/tools/net/ynl/pyynl/cli.py +++ b/tools/net/ynl/pyynl/cli.py @@ -17,9 +17,7 @@ import textwrap # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException - -SYS_SCHEMA_DIR='/usr/share/ynl' -RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink' +from lib import list_families # pylint: disable=too-few-public-methods,too-many-locals class Colors: @@ -48,30 +46,6 @@ def term_width(): """ Get terminal width in columns (80 if stdout is not a terminal) """ return shutil.get_terminal_size().columns -def schema_dir(): - """ - Return the effective schema directory, preferring in-tree before - system schema directory. - """ - script_dir = os.path.dirname(os.path.abspath(__file__)) - schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") - if not os.path.isdir(schema_dir_): - schema_dir_ = SYS_SCHEMA_DIR - if not os.path.isdir(schema_dir_): - raise YnlException(f"Schema directory {schema_dir_} does not exist") - return schema_dir_ - -def spec_dir(): - """ - Return the effective spec directory, relative to the effective - schema directory. - """ - spec_dir_ = schema_dir() + '/specs' - if not os.path.isdir(spec_dir_): - raise YnlException(f"Spec directory {spec_dir_} does not exist") - return spec_dir_ - - class YnlEncoder(json.JSONEncoder): """A custom encoder for emitting JSON with ynl-specific instance types""" def default(self, o): @@ -272,9 +246,8 @@ def main(): pprint.pprint(msg, width=term_width(), compact=True) if args.list_families: - for filename in sorted(os.listdir(spec_dir())): - if filename.endswith('.yaml'): - print(filename.removesuffix('.yaml')) + for family in list_families(): + print(family) return if args.no_schema: @@ -284,28 +257,23 @@ def main(): if args.json_text: attrs = json.loads(args.json_text) - if args.family: - spec = f"{spec_dir()}/{args.family}.yaml" - else: - spec = args.spec - if not os.path.isfile(spec): - raise YnlException(f"Spec file {spec} does not exist") + if args.spec and not os.path.isfile(args.spec): + raise YnlException(f"Spec file {args.spec} does not exist") + # Spec/YnlFamily will raise if both or neither spec and family are given if args.validate: + # Force validation even for installed specs (schema=True), unless the + # user explicitly picked a schema or opted out with --no-schema. + schema = True if args.schema is None else args.schema try: - SpecFamily(spec, args.schema) + SpecFamily(args.spec, schema_path=schema, family=args.family) except SpecException as error: print(error) sys.exit(1) return - if args.family: # set behaviour when using installed specs - if args.schema is None and spec.startswith(SYS_SCHEMA_DIR): - args.schema = '' # disable schema validation when installed - if args.process_unknown is None: - args.process_unknown = True - - ynl = YnlFamily(spec, args.schema, args.process_unknown, + ynl = YnlFamily(args.spec, schema=args.schema, family=args.family, + process_unknown=args.process_unknown, recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py index be741985ae4e..aa4263c8cba9 100644 --- a/tools/net/ynl/pyynl/lib/__init__.py +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -5,12 +5,13 @@ from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \ SpecException +from .specdir import list_families from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException from .doc_generator import YnlDocGenerator __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat", - "SpecException", + "SpecException", "list_families", "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException", "YnlDocGenerator"] diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py index 0469a0e270d0..b4ec59814ab1 100644 --- a/tools/net/ynl/pyynl/lib/nlspec.py +++ b/tools/net/ynl/pyynl/lib/nlspec.py @@ -12,6 +12,8 @@ import importlib import os import yaml as pyyaml +from .specdir import find_spec, SYS_SCHEMA_DIR + class SpecException(Exception): """Netlink spec exception. @@ -444,7 +446,23 @@ class SpecFamily(SpecElement): except AttributeError: _yaml_loader = pyyaml.SafeLoader - def __init__(self, spec_path, schema_path=None, exclude_ops=None): + def __init__(self, spec_path=None, schema_path=None, exclude_ops=None, + family=None): + # schema_path selects how the spec is validated: + # None -- no preference: validate against the default schema, + # but trust (skip) installed specs selected by family= + # True -- always validate against the default schema + # path -- validate against this schema + # '' -- do not validate + if (spec_path is None) == (family is None): + raise ValueError("Specify exactly one of spec path or family name") + if family is not None: + spec_path = find_spec(family) + # Installed specs are assumed correct, so skip schema validation + # to save cycles unless the caller asked to validate. + if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR): + schema_path = '' + with open(spec_path, "r", encoding='utf-8') as stream: prefix = '# SPDX-License-Identifier: ' first = stream.readline().strip() @@ -465,7 +483,7 @@ class SpecFamily(SpecElement): self.proto = self.yaml.get('protocol', 'genetlink') self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - if schema_path is None: + if schema_path is None or schema_path is True: schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' if schema_path: with open(schema_path, "r", encoding='utf-8') as stream: diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py new file mode 100644 index 000000000000..fcea9b9fb7b0 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/specdir.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +""" +Locating YNL spec and schema files on disk. + +Resolves the directory holding the YAML specs (preferring an in-tree copy +over the installed system path) and maps family names to spec files. +""" + +import os + +SYS_SCHEMA_DIR='/usr/share/ynl' +RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink' + + +def schema_dir(): + """ + Return the effective schema directory, preferring in-tree before + system schema directory. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}") + if not os.path.isdir(schema_dir_): + schema_dir_ = SYS_SCHEMA_DIR + if not os.path.isdir(schema_dir_): + raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist") + return schema_dir_ + +def spec_dir(): + """ + Return the effective spec directory, relative to the effective + schema directory. + """ + spec_dir_ = schema_dir() + '/specs' + if not os.path.isdir(spec_dir_): + raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist") + return spec_dir_ + + +def find_spec(family): + """ Return the path to the YAML spec file for a family by name. """ + spec = f"{spec_dir()}/{family}.yaml" + if not os.path.isfile(spec): + raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}") + return spec + + +def list_families(): + """ Return the sorted names of all families with an installed spec. """ + return sorted(f.removesuffix('.yaml') + for f in os.listdir(spec_dir()) if f.endswith('.yaml')) diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py index 092d132edec1..8682bf588e1f 100644 --- a/tools/net/ynl/pyynl/lib/ynl.py +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -661,6 +661,14 @@ class YnlFamily(SpecFamily): """ YNL family -- a Netlink interface built from a YAML spec. + The spec can be selected either by file path (def_path=) or, when it + ships in a well-known location, by family name (family="xyz"); exactly + one of the two must be given. For example: + + from pyynl import YnlFamily + + ynl = YnlFamily(family="netdev") + Primary use of the class is to execute Netlink commands: ynl.<op_name>(attrs, ...) @@ -691,11 +699,16 @@ class YnlFamily(SpecFamily): ynl.get_policy(op_name, mode) -- query kernel policy for an op """ - def __init__(self, def_path, schema=None, process_unknown=False, - recv_size=0): - super().__init__(def_path, schema) + def __init__(self, def_path=None, schema=None, process_unknown=None, + recv_size=0, family=None): + super().__init__(def_path, schema, family=family) self.include_raw = False + # Specs from /usr (selected by family=) have a higher chance of being + # stale, default to ignoring unknown attrs. In-tree users, and users + # who bundle the spec need to make a conscious decision. + if process_unknown is None: + process_unknown = family is not None self.process_unknown = process_unknown try: diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py index db3b62c652e7..0ee0c8e87686 100755 --- a/tools/net/ynl/tests/ethtool.py +++ b/tools/net/ynl/tests/ethtool.py @@ -11,12 +11,10 @@ import pathlib import pprint import sys import re -import os # pylint: disable=no-name-in-module,wrong-import-position sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) # pylint: disable=import-error -from cli import schema_dir, spec_dir from lib import YnlFamily @@ -173,10 +171,7 @@ def main(): args = parser.parse_args() - spec = os.path.join(spec_dir(), 'ethtool.yaml') - schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') - - ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv) + ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv) if args.dbg_small_recv: ynl.set_recv_dbg(True) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 6eb9096d084c..477aedbb01ba 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; @@ -1514,7 +1514,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/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/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/lib.sh b/tools/testing/selftests/net/lib.sh index d46d2cec89e4..f13672c985bf 100644 --- a/tools/testing/selftests/net/lib.sh +++ b/tools/testing/selftests/net/lib.sh @@ -72,13 +72,9 @@ ksft_exit_status_merge() timestamp_ms() { - local now - local seconds - local nanoseconds - - now=$(date -u +%s:%N) || return - seconds=${now%:*} - nanoseconds=${now#*:} + local now=$(date -u +%s:%N) + local seconds=${now%:*} + local nanoseconds=${now#*:} if [[ $nanoseconds =~ ^[0-9]+$ ]]; then nanoseconds=${nanoseconds:0:9} @@ -93,10 +89,9 @@ loopy_wait() { local sleep_cmd=$1; shift local timeout_ms=$1; shift - local start_time local current_time - start_time=$(timestamp_ms) || return + local start_time=$(timestamp_ms) while true do local out @@ -105,7 +100,7 @@ loopy_wait() return 0 fi - current_time=$(timestamp_ms) || return + local current_time=$(timestamp_ms) if ((current_time - start_time > timeout_ms)); then echo -n "$out" return 1 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/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" + ] } ] |
