summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-06 14:38:12 +0100
committerMark Brown <broonie@kernel.org>2026-07-06 14:38:12 +0100
commitcf4d44c65ca6b17a91e38b11cb6a4cafb31961b4 (patch)
treea026d391aee904c917eaaacb319149485af7b3a5 /tools
parenta4cddd6db711ad1564aff4298cdffe4e874810ea (diff)
parent1704cc8640702c93e79921e2dffff3cfa31f0ce5 (diff)
downloadlinux-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__.py9
-rwxr-xr-xtools/net/ynl/pyynl/cli.py56
-rw-r--r--tools/net/ynl/pyynl/lib/__init__.py3
-rw-r--r--tools/net/ynl/pyynl/lib/nlspec.py22
-rw-r--r--tools/net/ynl/pyynl/lib/specdir.py51
-rw-r--r--tools/net/ynl/pyynl/lib/ynl.py19
-rwxr-xr-xtools/net/ynl/tests/ethtool.py7
-rw-r--r--tools/testing/selftests/bpf/prog_tests/test_xsk.c4
-rwxr-xr-xtools/testing/selftests/drivers/net/hw/toeplitz.py22
-rw-r--r--tools/testing/selftests/net/getsockopt_iter.c97
-rw-r--r--tools/testing/selftests/net/lib.sh15
-rwxr-xr-xtools/testing/selftests/net/rtnetlink.py101
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json164
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"
+ ]
}
]