BPF List
 help / color / mirror / Atom feed
From: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
To: <netdev-bot+sashiko@kernel.org>
Cc: <netdev@vger.kernel.org>, <bpf@vger.kernel.org>,
	<magnus.karlsson@intel.com>, <stfomichev@gmail.com>,
	<kuba@kernel.org>, <pabeni@redhat.com>,
	<tushar.vyavahare@intel.com>, <kerneljasonxing@gmail.com>,
	<bjorn@kernel.org>
Subject: Re: [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer
Date: Sat, 17 Oct 2026 20:50:39 +0200	[thread overview]
Message-ID: <atPDf2DpOfWLt9cT@boxer> (raw)
In-Reply-To: <179220637173.434549.12610717858136223745@kernel.org>

On Sat, Oct 17, 2026 at 03:06:11AM +0000, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 8 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 0 · Medium: 6 · Low: 2
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [Medium] In tools/testing/selftests/drivers/net/hw/xsk.py, main()
>   changes state on both hosts and has no try/finally to undo it.
> - [Medium] In xsk.py, _run() starts the DUT zero-copy endpoint with
>   `local = cmd(local_args, background=True, fail=False)` at line 142 and…
> - [Medium] main() in xsk.py calls `xsk_reserve_hugepages(saved_hugepages,
>   cfg.remote)` (line 227), and xsk_reserve_hugepages()…
> - [Medium] If an endpoint is killed, the NIC's TX/RX descriptor ring
>   sizes are never restored.
> - [Medium] Taking the zero-copy queue out of RSS (_reserve_queue(),
>   xsk.py line 74) is the only thing keeping non-test traffic off it.
> - [Medium] _reserve_queue() in xsk.py assumes every reported channel can
>   be addressed by RSS.
> - [Low] The commit message says 'A host without ntuple support skips' and
>   'A DUT with fewer than two such queues skips'.
> - [Low] In xsk.py, _steer_udp() runs `ethtool -K <if> ntuple-filters on`
>   (line 86) when the feature is inactive and not fixed.
> 

I believe here I am done with responding to comments from our beloved
bot. There is one issue that indeed needs a fix before getting this in
(see below at ATR comment). Let me know if any fixups are possible...

> --
> 
> selftests: drv-net: test AF_XDP zero-copy with an SKB peer
> 
> This adds drivers/net/hw/xsk.py. It runs the xskxceiver cases as pairs of
> processes: a zero-copy endpoint on the DUT and an SKB-mode endpoint on the
> remote. main() sets up the shared state once (an RSS-excluded queue, ntuple
> rules on both hosts, huge pages) and undoes it after ksft_run().
> 
> > diff --git a/tools/testing/selftests/drivers/net/hw/xsk.py b/tools/testing/selftests/drivers/net/hw/xsk.py
> > new file mode 100755
> > index 000000000000..fd708bb3420d
> > --- /dev/null
> > +++ b/tools/testing/selftests/drivers/net/hw/xsk.py
> > @@ -0,0 +1,263 @@
> 
> [ ... ]
> 
> > +def _reserve_queue(cfg):
> > +    """Take the last queue with both an RX and a TX ring out of RSS. A TX
> > +    endpoint fills no RX buffers, so in zero-copy mode its queue drops what
> > +    it receives, the control connection included."""
> > +    ch = cfg.ethnl.channels_get({"header": {"dev-index": cfg.ifindex}})
> > +    count = ch.get("combined-count", 0) + min(ch.get("rx-count", 0),
> > +                                              ch.get("tx-count", 0))
> 
> [Severity: Medium]
> Does this assume that the RSS indirection table can address every reported
> channel?
> 
> On pre-X550 ixgbe (82599/X540) with ATR enabled, which is the default,
> ixgbe_get_channels() reports the Flow Director queue count:
> 
> drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c:ixgbe_get_channels() {
>     ...
> 	/* report flow director queues as maximum channels */
> 	ch->combined_count = adapter->ring_feature[RING_F_FDIR].indices;
>     ...
> }
> 
> That count can be as high as 64. The RSS table on those parts can only
> address 16 queues:
> 
> ixgbe_rss_indir_tbl_max() {
> 	if (adapter->hw.mac.type < ixgbe_mac_X550)
> 		return 16;
>     ...
> }
> 
> ixgbe_set_rxfh() rejects entries beyond that limit:
> 
> 	for (i = 0; i < reta_entries; i++)
> 		if (rxfh->indir[i] >= max_queues)
> 			return -EINVAL;
> 
> On a host with more than 17 CPUs, would "ethtool -X <netif> equal 63" fail
> and raise CmdExitFailure during setup? That would abort the suite on a NIC
> that advertises AF_XDP zero-copy. In this configuration the chosen queue is
> already outside RSS, so the table doesn't need to change.
> 
> > +    if count < 2:
> > +        return None
> > +    queue = count - 1
> > +    ethtool(f"-X {cfg.ifname} equal {queue}")

in v4 rejected ethtool -X skips the run instead of raising exitfailure and
any previous setup is undone.

Current get_channels() output interpretation is targeted towards modern
NICs, but later on if there is really such need we could check if chosen
queue is already out of RSS...?

> 
> [Severity: Medium]
> On NICs that steer flows based on TX, is taking the queue out of RSS
> enough to keep the TCP control connection off it?
> 
> Flow Director ATR is on by default on i40e. i40e_atr() samples TCP TX
> packets, including the SYN. It then programs an FD filter that steers the
> flow's RX to the queue of the TX ring:
> 
> drivers/net/ethernet/intel/i40e/i40e_txrx.c:i40e_atr() {
>     ...
> 	flex_ptype = FIELD_PREP(I40E_TXD_FLTR_QW0_QINDEX_MASK,
> 				tx_ring->queue_index);
>     ...
> }

SO CLOSE! This one has to be addressed as atr is turned on by default and
this could cause i40e runs being stochastic. I must admit I did not have
i40e setup on my side, but it has full AF_XDP feature support (including
mbuf zc) so its coverage matters.

Fix would be:

diff --git a/tools/testing/selftests/drivers/net/hw/xsk.py b/tools/testing/selftests/drivers/net/hw/xsk.py
--- a/tools/testing/selftests/drivers/net/hw/xsk.py
+++ b/tools/testing/selftests/drivers/net/hw/xsk.py
@@ -115,6 +115,16 @@ def _reserve_queue(cfg, undo):
     return queue
 
 
+def _disable_atr(cfg, undo):
+    """i40e ATR steers what a TCP connection receives to the queue that it
+    sends from, which can be the zero-copy one, so turn it off until undo."""
+    flags = cmd(f"ethtool --show-priv-flags {cfg.ifname}", fail=False).stdout
+    if re.search(r"^flow-director-atr\s*: on$", flags, re.MULTILINE):
+        priv = f"--set-priv-flags {cfg.ifname} flow-director-atr"
+        ethtool(f"{priv} off")
+        undo.callback(ethtool, f"{priv} on")
+
+
 def _steer_udp(cfg, undo, ifname, queue, host=None):
     """Steer the test flow to queue until undo, return False if ifname
     cannot."""
@@ -313,6 +323,7 @@ def main():
         cfg.queue = _reserve_queue(cfg, undo)
         if cfg.queue is None:
             sys.exit(SKIP_CODE)
+        _disable_atr(cfg, undo)
 
         cfg.udp_port = rand_port()
         steered = (_steer_udp(cfg, undo, cfg.ifname, cfg.queue) and

> 
> FD filters take priority over RSS. Suppose XPS picks the last TX queue for
> the CPU that the DUT xskxceiver runs on. The remote's replies on the
> control connection would then land on the zero-copy queue.
> 
> In TX cases that queue has no fill buffers. Wouldn't the control
> connection stall, so the case fails or times out depending on CPU
> placement?
> 
> Only a udp4 ntuple rule is installed. Unlike a TCP4 sideband rule, it
> doesn't disable ATR automatically, and the flow-director-atr private flag
> is not cleared.
> 
> Some drivers gate aRFS on NETIF_F_NTUPLE, which _steer_udp() may turn on.
> When RFS is configured, aRFS could steer flows the same way.
> 
> > +    return queue
> > +
> > +
> > +def _steer_udp(cfg, ifname, port, queue, host=None):
> > +    """Steer the test flow to queue, return False if ifname cannot."""
> > +    ntuple = ethtool(f"-k {ifname}", json=True, host=host)[0]["ntuple-filters"]
> > +    if not ntuple["active"]:
> > +        if ntuple["fixed"]:
> > +            print(f"1..0 # SKIP {ifname} does not support ntuple-filters")
> > +            return False
> > +
> > +        ethtool(f"-K {ifname} ntuple-filters on", host=host)
> > +
> > +    out = ethtool(f"-N {ifname} flow-type udp4 dst-port {port} action {queue}",
> > +                  host=host).stdout
> > +    rule = re.search(r"ID (\d+)", out).group(1)
> 
> [Severity: Low]
> The commit message says:
> 
>     A host without ntuple support skips.
> 
> and:
> 
>     A DUT with fewer than two such queues skips
> 
> Here a skip happens only when ntuple-filters is fixed off. There are two
> other failure cases:
> 
> - The feature can be toggled but the NIC rejects the udp4 dst-port rule.
>   ethtool -N then raises CmdExitFailure.
> - The output has no "ID N". The re.search(...).group(1) then raises
>   AttributeError.
> 
> Would either of these bypass the skip branch in main()? The run would then
> error out instead of skipping, and the _unsteer_udp() and "ethtool -X
> default" calls in that branch would never run. If only the remote rule
> fails, the DUT rule that was already added would also be left behind.
> 
> Similarly, _reserve_queue() runs "ethtool -X equal" with fail=True. A DUT
> that rejects the RSS table update, such as the pre-X550 ixgbe case above,
> would crash during setup instead of skipping.
> 
> > +    cfg.rules.append((host, ifname, rule, ntuple["active"]))
> 
> [Severity: Low]
> When ntuple-filters was inactive, "ethtool -K ... ntuple-filters on" turns
> it on before the rule is added. The toggle is recorded only here, after
> both ethtool -N and the ID parse succeed.
> 
> If either of those raises, the toggle is never recorded. _unsteer_udp()
> turns the feature off only for entries in cfg.rules.
> 
> Could ntuple-filters be left enabled on that host after a failed run?
> Recording the undo right after the toggle would avoid that.
> 
> > +    return True
> 
> [ ... ]
> 
> > +def _setup(cfg, busy_poll):
> 
> [ ... ]
> 
> > +    fallbacks = [
> > +        defer(ip, f"link set dev {cfg.ifname} mtu {cfg.dev['mtu']}"),
> > +        defer(ip, f"link set dev {cfg.remote_ifname} "
> > +                  f"mtu {cfg.remote_dev['mtu']}", host=cfg.remote),
> > +        defer(ip, f"link set dev {cfg.ifname} xdpdrv off"),
> > +        defer(ip, f"link set dev {cfg.remote_ifname} xdpgeneric off",
> > +              host=cfg.remote),
> > +    ]
> 
> [Severity: Medium]
> What restores the NIC ring sizes when an endpoint is killed?
> 
> xskxceiver changes ring sizes with set_hw_ring_size(). The only restore
> happens in cleanup_iface(), on normal exit:

Addressed in v4 by moving ring changes into  xsk.py . The runner saves the
sizes and registers their restoration before starting the endpoints, so
restoration does not depend on endpoint cleanup running.

> 
> tools/testing/selftests/net/lib/xsk/xskxceiver.c:cleanup_iface() {
>     ...
> 	if (ifobj_has_cap(ifobj, XSK_CAP_HW_RING))
> 		hw_ring_size_reset(ifobj);
>     ...
> }
> 
> xskxceiver has no signal handling, so this restore is skipped in two
> cases:
> 
> - xsk_wait_endpoint_exit() sends SIGKILL to the DUT endpoint after
>   CASE_TIMEOUT.
> - timeout(1) sends SIGTERM to the remote endpoint.
> 
> These fallbacks cover only the MTU and the XDP detach.
> 
> The next endpoint's capability probe in test_xsk.c then adopts the
> leftover size as its default:
> 
> 	if (!get_hw_ring_size(ifobj->ifname, &ifobj->ring)) {
> 		ifobj_set_cap(ifobj, XSK_CAP_HW_RING);
> 		...
> 		ifobj->set_ring.default_tx = ifobj->ring.tx_pending;
> 		ifobj->set_ring.default_rx = ifobj->ring.rx_pending;
> 
> Would a hung ring size case then leave the NIC with a min or max
> descriptor ring size for the rest of the run, and after it?
> 
> > +    return fallbacks
> 
> [ ... ]
> 
> > +def _run(cfg, local_args, remote_args, ctrl_port):
> 
> [ ... ]
> 
> > +    try:
> > +        head, listening = xsk_wait_listen(remote, ctrl_port, LISTEN_TIMEOUT)
> > +        if listening:
> > +            local = cmd(local_args, background=True, fail=False)
> > +            dut_ret = xsk_wait_endpoint_exit(local, "dut", CASE_TIMEOUT)
> > +    finally:
> > +        # Even if the DUT endpoint did not run, so that the remote one is
> > +        # gone before the next case starts.
> > +        remote_ret = xsk_wait_endpoint_exit(remote, "remote", REMOTE_TIMEOUT,
> > +                                            head)
> 
> [Severity: Medium]
> Can the DUT endpoint be orphaned here? The finally reaps only the remote
> endpoint, and xsk_wait_endpoint_exit() kills the process only on a
> timeout:
> 
> tools/testing/selftests/net/lib/py/xsk.py:xsk_wait_endpoint_exit() {
>     try:
>         endpoint.process(terminate=False, fail=False, timeout=timeout)
>     except subprocess.TimeoutExpired:
>         endpoint.proc.kill()
>     ...
> }

Addressed in v4 in the shared helper: interruption kills and reaps the
endpoint before propagating the exception.

> 
> When the kselftest runner times out, it sends SIGTERM to the Python
> process only. _ksft_intr then raises KsftTerminate inside the wait on
> local, and the exception propagates while local.proc is still running.
> 
> ksft_run() catches the exception and runs ksft_flush_defer(). That
> detaches the XDP program and restores the MTU while the AF_XDP socket is
> still bound. main() then deletes the ntuple rules and resets RSS.
> 
> xskxceiver would keep running after Python exits. It would later run its
> own cleanup_iface() against state that the runner has already changed.
> 
> Should local also be killed in the finally?
> 
> > +    return dut_ret, remote_ret
> 
> [ ... ]
> 
> > +def main():
> > +    # ksft_run() exits on -l and -h before main() can undo the setup.
> > +    if "-l" in sys.argv or "-h" in sys.argv:
> > +        sys.exit(f"{sys.argv[0]}: -l and -h are not supported")
> 
> [ ... ]
> 
> > +        saved_hugepages = []
> > +        cfg.hugepages = (xsk_reserve_hugepages(saved_hugepages) and
> > +                         xsk_reserve_hugepages(saved_hugepages, cfg.remote))
> 
> [Severity: Medium]
> What happens when the remote kernel is built without CONFIG_HUGETLBFS?
> xsk_reserve_hugepages() starts with:

addressed in v4 and described on veth runner comments

> 
>     have = int(cmd("sysctl -n vm.nr_hugepages", host=host).stdout)
> 
> vm.nr_hugepages exists only with CONFIG_HUGETLBFS (mm/hugetlb_sysctl.c).
> Without it the sysctl fails, and cmd() raises CmdExitFailure instead of
> the helper returning False.
> 
> The commit message says the UNALIGNED cases are "left out without them".
> Wouldn't the whole suite abort before ksft_run() instead, leaving the RSS
> change and ntuple rules in place?
> 
> The drivers/net/hw config covers the DUT, but nothing constrains the
> remote kernel.
> 
> > +
> > +        cases = _get_cases(cfg)
> 
> [ ... ]
> 
> > +        ksft_run([ksft_variants(cases)(test_xsk),
> > +                  ksft_variants(cases)(test_xsk_busy_poll)], args=(cfg,))
> > +
> > +        xsk_release_hugepages(saved_hugepages)
> > +        _unsteer_udp(cfg)
> > +        ethtool(f"-X {cfg.ifname} default")
> 
> [Severity: Medium]
> If main() leaves early, does anything undo the RSS change, the ntuple
> rules and the huge page reservation? These three calls run only when
> ksft_run() returns normally, and the setup has no try/finally around it.
> NetDrvEpEnv.__exit__ doesn't know about cfg.rules or saved_hugepages, and
> defer() only works inside a case.
> 
> Any exception after _reserve_queue() skips all three calls. Some examples:

addressed in v4 via ExitStack introduction, suggested by Bjorn

> 
> - a CmdExitFailure from ethtool -N on either host
> - the AttributeError from the rule ID parse
> - a failing remote sysctl
> - a failing "xskxceiver -L" in _get_cases()
> 
> The "-l"/"-h" guard at the top of main() matches only those exact tokens.
> ksft_run() parses arguments with:
> 
>     opts, _ = getopt.getopt(sys.argv[1:], 'hlt:T:')
> 
> This accepts clustered options like "-lt NAME" or "-hl". It also calls
> sys.exit(1) on a GetoptError, such as an unknown option, --help, or -t
> with no value. All of these exit from inside ksft_run(), after the setup
> has already run.
> 
> The commit message gives the reason for the guard as:
> 
>     so xsk.py rejects -l and -h, on which ksft_run() exits without
>     returning
> 
> sys.exit() raises SystemExit. Would a try/finally around the setup and
> ksft_run() cover these paths and the -l/-h case as well?
> 
> There are two smaller points about the teardown itself:
> 
> - "ethtool -X default" installs the driver's default indirection table,
>   not the one that was in place before the run. A custom table set by
>   the admin is lost.
> - If xsk_release_hugepages() fails, for example over ssh, the rule
>   deletes and the RSS reset after it are skipped.
> 
> > +    ksft_exit()
> 
> -- 
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261014184539.774708-1-maciej.fijalkowski%40intel.com

  reply	other threads:[~2026-10-17 18:50 UTC|newest]

Thread overview: 50+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 18:45 [PATCH v3 net-next 00/15] selftests: net: migrate AF_XDP test suite over to net Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 01/15] selftests: xsk: factor endpoint work out of pthread wrappers Maciej Fijalkowski
2026-10-15  8:14   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 02/15] selftests: xsk: size the TX UMEM in HW_SW_MAX_RING_SIZE Maciej Fijalkowski
2026-10-15  8:15   ` Björn Töpel
2026-10-15 18:46   ` sashiko-bot
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 14:40     ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 03/15] selftests: xsk: drop the single-interface loopback mode Maciej Fijalkowski
2026-10-15  8:17   ` Björn Töpel
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 14:50     ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 04/15] selftests/bpf: drop the test_progs AF_XDP wrapper Maciej Fijalkowski
2026-10-15  8:18   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 05/15] selftests: net: add a generic rule for BPF skeletons Maciej Fijalkowski
2026-10-15  8:20   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 06/15] selftests: xsk: move the AF_XDP test suite to selftests/net Maciej Fijalkowski
2026-10-15  8:22   ` Björn Töpel
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 15:31     ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 07/15] selftests: xsk: collect interface capabilities in struct xsk_caps Maciej Fijalkowski
2026-10-15  8:27   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 08/15] selftests: xsk: split xskxceiver main() into setup, run and cleanup Maciej Fijalkowski
2026-10-15  8:29   ` Björn Töpel
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-14 18:45 ` [PATCH v3 net-next 09/15] selftests: xsk: run one test case per xskxceiver invocation Maciej Fijalkowski
2026-10-15  8:34   ` Björn Töpel
2026-10-15 18:46   ` sashiko-bot
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 16:04     ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 10/15] selftests: xsk: run the RX and TX endpoints in separate processes Maciej Fijalkowski
2026-10-15  9:04   ` Björn Töpel
2026-10-14 18:45 ` [PATCH v3 net-next 11/15] selftests: xsk: add a hardware mode to xskxceiver Maciej Fijalkowski
2026-10-15  9:42   ` Björn Töpel
2026-10-15 10:55     ` Maciej Fijalkowski
2026-10-15 16:03       ` Björn Töpel
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 17:40     ` Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 12/15] selftests: xsk: pass non-test traffic to the stack in hardware mode Maciej Fijalkowski
2026-10-15 10:05   ` Björn Töpel
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-14 18:45 ` [PATCH v3 net-next 13/15] selftests: xsk: list the hardware test cases Maciej Fijalkowski
2026-10-14 18:45 ` [PATCH v3 net-next 14/15] selftests: drv-net: test AF_XDP zero-copy with an SKB peer Maciej Fijalkowski
2026-10-15 10:29   ` Björn Töpel
2026-10-15 12:52     ` Maciej Fijalkowski
2026-10-15 18:46   ` sashiko-bot
2026-10-17  3:06   ` netdev-bot+sashiko
2026-10-17 18:50     ` Maciej Fijalkowski [this message]
2026-10-14 18:45 ` [PATCH v3 net-next 15/15] selftests: xsk: document generic and hardware endpoint runs Maciej Fijalkowski
2026-10-17  3:06   ` netdev-bot+sashiko

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=atPDf2DpOfWLt9cT@boxer \
    --to=maciej.fijalkowski@intel.com \
    --cc=bjorn@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=kerneljasonxing@gmail.com \
    --cc=kuba@kernel.org \
    --cc=magnus.karlsson@intel.com \
    --cc=netdev-bot+sashiko@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=stfomichev@gmail.com \
    --cc=tushar.vyavahare@intel.com \
    /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