Netdev List
 help / color / mirror / Atom feed
From: Reshma Sreekumar <reshmaisat@gmail.com>
To: Jamal Hadi Salim <jhs@mojatatu.com>,
	Jiri Pirko <jiri@resnulli.us>,
	netdev@vger.kernel.org
Cc: "David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Simon Horman <horms@kernel.org>, Shuah Khan <shuah@kernel.org>,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
	Reshma Sreekumar <reshmaisat@gmail.com>
Subject: [PATCH net 2/2] selftests: net: check that a disturbed qdisc dump is flagged
Date: Thu, 30 Jul 2026 12:00:06 +0200	[thread overview]
Message-ID: <20260730100006.3547944-3-reshmaisat@gmail.com> (raw)
In-Reply-To: <20260730100006.3547944-1-reshmaisat@gmail.com>

Add a test for NLM_F_DUMP_INTR on RTM_GETQDISC.  A qdisc dump spans several
netlink batches once a few devices carry a full set of qdiscs, and RTNL is
only held while a single batch is filled, so the device list can change in
between and entries can be missed.

Netlink dumps are driven by the reader, so rather than racing against a
dump the test unregisters a device at a known point: it receives the first
batch, unregisters a dummy, then drains the rest.  This is deterministic --
one flagged message, every run.

A second case dumps without disturbing anything and requires that the flag
stays clear, so the test cannot be satisfied by a kernel that raises it
unconditionally.

Four dummy devices with mq + 16 fq + clsact each, read with a 4KiB buffer,
give 12 batches and run in about a second.  The test skips if dummy, mq, fq
or clsact are unavailable.

Signed-off-by: Reshma Sreekumar <reshmaisat@gmail.com>
---
 tools/testing/selftests/net/Makefile          |   1 +
 .../testing/selftests/net/qdisc_dump_intr.py  | 123 ++++++++++++++++++
 2 files changed, 124 insertions(+)
 create mode 100755 tools/testing/selftests/net/qdisc_dump_intr.py

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae..769ee26ae 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -72,6 +72,7 @@ TEST_PROGS := \
 	pmtu.sh \
 	protodown.sh \
 	psock_snd.sh \
+	qdisc_dump_intr.py \
 	reuseaddr_ports_exhausted.sh \
 	reuseport_addr_any.sh \
 	route_hint.sh \
diff --git a/tools/testing/selftests/net/qdisc_dump_intr.py b/tools/testing/selftests/net/qdisc_dump_intr.py
new file mode 100755
index 000000000..9623847f4
--- /dev/null
+++ b/tools/testing/selftests/net/qdisc_dump_intr.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""
+Check that a qdisc dump disturbed by a device unregister reports
+NLM_F_DUMP_INTR.
+
+tc_dump_qdisc() walks every netdev in the netns and spans several netlink
+batches once there are more than a handful of qdiscs.  RTNL is only held
+while a single batch is filled, so the device list can change in between and
+the reply can miss entries.  Netlink dumps are driven by the reader, so this
+test unregisters a device at a known point between two batches rather than
+racing against one.
+"""
+
+import socket
+import struct
+
+from lib.py import ksft_run, ksft_exit, ksft_true, ksft_pr, KsftSkipEx
+from lib.py import CmdExitFailure, NetNS, NetNSEnter, ip, tc
+
+NETLINK_ROUTE = 0
+RTM_NEWQDISC = 36
+RTM_GETQDISC = 38
+NLMSG_DONE = 3
+NLM_F_REQUEST = 0x001
+NLM_F_DUMP = 0x300
+NLM_F_DUMP_INTR = 0x010
+
+NLMSGHDR = "=IHHII"
+TCMSG_LEN = 20          # struct tcmsg
+
+# tc parses handles as hex; 64: keeps the mq root clear of the fq children
+MQ_HANDLE = "64"
+CHANNELS = 16           # fq children per device
+NDEVS = 4               # 4 * (mq + 16 fq + clsact) is several batches worth
+BUFSZ = 4096            # small recv buffer keeps the kernel's batches small
+
+
+def _setup():
+    """Devices carrying enough qdiscs that a dump has to paginate."""
+    try:
+        for i in range(NDEVS):
+            ip(f"link add d{i} numtxqueues {CHANNELS} type dummy")
+            ip(f"link set d{i} up")
+            tc(f"qdisc replace dev d{i} root handle {MQ_HANDLE}: mq")
+            for q in range(1, CHANNELS + 1):
+                tc(f"qdisc replace dev d{i} parent {MQ_HANDLE}:{q:x} "
+                   f"handle {q:x}: fq")
+            tc(f"qdisc replace dev d{i} clsact")
+    except CmdExitFailure as exc:
+        raise KsftSkipEx("dummy, mq, fq or clsact unavailable") from exc
+
+
+def _dump(between_batches=None):
+    """
+    Walk an RTM_GETQDISC dump one batch at a time, optionally running
+    `between_batches` after the first batch.  Returns (batches, qdiscs,
+    messages carrying NLM_F_DUMP_INTR).
+    """
+    sk = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_ROUTE)
+    try:
+        sk.bind((0, 0))
+        hdr = struct.pack(NLMSGHDR, struct.calcsize(NLMSGHDR) + TCMSG_LEN,
+                          RTM_GETQDISC, NLM_F_REQUEST | NLM_F_DUMP, 1, 0)
+        sk.send(hdr + bytes(TCMSG_LEN))
+
+        batches = qdiscs = intr = 0
+        done = False
+        while not done:
+            buf = sk.recv(BUFSZ)
+            batches += 1
+            off = 0
+            while off + 16 <= len(buf):
+                mlen, mtype, flags = struct.unpack_from("=IHH", buf, off)
+                if mlen < 16:
+                    break
+                if flags & NLM_F_DUMP_INTR:
+                    intr += 1
+                if mtype == NLMSG_DONE:
+                    done = True
+                elif mtype == RTM_NEWQDISC:
+                    qdiscs += 1
+                off += (mlen + 3) & ~3
+            if batches == 1 and not done and between_batches:
+                between_batches()
+        return batches, qdiscs, intr
+    finally:
+        sk.close()
+
+
+def dump_intr_on_unregister() -> None:
+    """Unregistering a device mid-dump must raise NLM_F_DUMP_INTR."""
+    ip("link add victim type dummy")
+
+    batches, qdiscs, intr = _dump(lambda: ip("link del victim"))
+
+    ksft_pr(f"{batches} batches, {qdiscs} qdiscs, {intr} flagged messages")
+    ksft_true(batches > 1, "dump did not paginate, nothing to disturb")
+    ksft_true(intr > 0,
+              "NLM_F_DUMP_INTR not set although a device was unregistered "
+              "while the dump was in flight")
+
+
+def dump_intr_absent_when_stable() -> None:
+    """An undisturbed dump must not raise NLM_F_DUMP_INTR."""
+    batches, qdiscs, intr = _dump()
+
+    ksft_pr(f"{batches} batches, {qdiscs} qdiscs, {intr} flagged messages")
+    ksft_true(batches > 1, "dump did not paginate, test is not meaningful")
+    ksft_true(intr == 0, "NLM_F_DUMP_INTR set on an undisturbed dump")
+
+
+def main() -> None:
+    with NetNS() as ns:
+        with NetNSEnter(str(ns)):
+            _setup()
+            ksft_run(globs=globals(), case_pfx={"dump_"})
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()
-- 
2.43.0


      parent reply	other threads:[~2026-07-30 10:00 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 10:00 [PATCH net 0/2] net/sched: flag inconsistent qdisc dumps Reshma Sreekumar
2026-07-30 10:00 ` [PATCH net 1/2] " Reshma Sreekumar
2026-07-30 10:25   ` Eric Dumazet
2026-07-30 10:00 ` Reshma Sreekumar [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730100006.3547944-3-reshmaisat@gmail.com \
    --to=reshmaisat@gmail.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox