Netdev List
 help / color / mirror / Atom feed
From: Todd Freed <toddfreed@meta.com>
To: <netdev@vger.kernel.org>, <toddfreed@meta.com>
Cc: Daniel Zahka <daniel.zahka@gmail.com>,
	Jakub Kicinski <kuba@kernel.org>,
	Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	Shuah Khan <shuah@kernel.org>,
	Rahul Rameshbabu <rrameshbabu@nvidia.com>,
	Raed Salem <raeds@nvidia.com>, Cosmin Ratiu <cratiu@nvidia.com>,
	<linux-kselftest@vger.kernel.org>
Subject: [PATCH net 2/2] selftests: drv-net: psp: test ipv6 tcp socket with extension headers
Date: Wed, 21 Oct 2026 14:02:00 -0700	[thread overview]
Message-ID: <20261021210200.788191-3-toddfreed@meta.com> (raw)
In-Reply-To: <20261021210200.788191-1-toddfreed@meta.com>

send on a psp tcp socket in three phases, checking the device's
tx-packets and what the peer received after each

1/ no socket options - the data is sent and counted.

2/ an extension header set on the socket - assert that the packets are
   dropped, that tx-packets is unchanged, and that nothing reaches the
   peer. the data stays queued.

3/ option removed - the queued data and new data are sent and counted,
   and the peer gets all of it.

phases 2 and 3 run twice, with a destination options header
(IPV6_DSTOPTS) and with a hop-by-hop options header (IPV6_HOPOPTS), each
holding one padn option.

in phase 2, a software filter on the peer also counts udp packets from
the socket's address to the peer's. the well-formed psp packets reach
the peer as decrypted tcp packets, as expected. the malformed packets
show up as udp. if the peer can't take the filter, that check is
skipped.

tx-packets is read after it has quiesced, because some devices refresh
counter periodically (mlx5 takes about a second).

without the fix in the preceding patch:

- with netdevsim, the packets of phase 2 are encapsulated and counted in
  tx-packets, and the test fails on that.
- with mlx5 (connectx-7), the packets go out unencrypted and aren't
  counted in tx-packets. the test fails on the udp packets which reach
  the peer. capturing on the peer,

    tcpdump -i <dev> -nn -A 'ip6 and src host <sender> and not tcp'

  shows them as udp, with the test's payload readable

Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 tools/testing/selftests/drivers/net/config |   1 +
 tools/testing/selftests/drivers/net/psp.py | 120 ++++++++++++++++++++-
 2 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config
index 4838adf27fa1..409f9a96a102 100644
--- a/tools/testing/selftests/drivers/net/config
+++ b/tools/testing/selftests/drivers/net/config
@@ -4,6 +4,7 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n
 CONFIG_INET_PSP=y
 CONFIG_IPV6=y
 CONFIG_MACSEC=m
+CONFIG_NET_ACT_GACT=m
 CONFIG_NET_ACT_SKBEDIT=m
 CONFIG_NET_CLS_ACT=y
 CONFIG_NET_CLS_BPF=y
diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index a5b1e14f120f..2a109ef61468 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -16,12 +16,12 @@ from lib.py import ksft_run, ksft_exit, ksft_pr
 from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises
 from lib.py import ksft_not_none
 from lib.py import ksft_variants, KsftNamedVariant
-from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import KsftSkipEx, KsftFailEx, CmdExitFailure
 from lib.py import NetDrvEpEnv, NetDrvContEnv
 from lib.py import Netlink, NlError, PSPFamily, RtnlFamily
 from lib.py import NetNSEnter
 from lib.py import bkg, rand_port, wait_port_listen
-from lib.py import ip
+from lib.py import ip, tc
 
 TCP_ULP = 31
 
@@ -120,6 +120,57 @@ def _check_data_outq(s, exp_len, force_wait=False):
 def _get_stat(cfg, key):
     return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key]
 
+
+def _get_quiesced_stat(cfg, key, timeout=10):
+    """
+    Like _get_stat(), but wait till the value stops changing. Note
+    that some devices refresh flow counters periodically, e.g. once
+    per second with mlx5.
+    """
+    val = _get_stat(cfg, key)
+    end = time.monotonic() + timeout
+    while True:
+        time.sleep(1.5)
+        new = _get_stat(cfg, key)
+        if new == val:
+            return val
+        val = new
+        if time.monotonic() >= end:
+            ksft_pr(f"{key} still changing after {timeout}s: {val}")
+            return val
+
+
+def _remote_udp_from(cfg, src, dst):
+    """
+    Count what reaches the remote from @src to @dst as UDP, with a software
+    filter on its ingress (offloaded filter counters are refreshed
+    periodically). Returns a function reading the count, or None if the
+    filter can't be installed.
+    """
+    flt = (f"dev {cfg.remote_ifname} ingress protocol ipv6 pref 1 "
+           f"handle 0x5053 flower")
+    try:
+        qdiscs = tc(f"qdisc show dev {cfg.remote_ifname}", json=True,
+                    host=cfg.remote)
+        if not any(q['kind'] == 'clsact' for q in qdiscs):
+            tc(f"qdisc add dev {cfg.remote_ifname} clsact", host=cfg.remote)
+            defer(tc, f"qdisc del dev {cfg.remote_ifname} clsact",
+                  host=cfg.remote)
+        tc(f"filter add {flt} skip_hw src_ip {src} dst_ip {dst} "
+           f"ip_proto udp action drop", host=cfg.remote)
+    except CmdExitFailure as e:
+        ksft_pr(f"Can't count UDP on the remote: {e}")
+        return None
+    defer(tc, f"filter del {flt}", host=cfg.remote)
+
+    def count():
+        filters = tc(f"-s filter get {flt}", json=True, host=cfg.remote)
+        for f in filters:
+            for act in f.get("options", {}).get("actions", []):
+                return act["stats"]["packets"]
+        raise KsftFailEx("no action on the remote filter")
+    return count
+
 #
 # Test case boiler plate
 #
@@ -624,6 +675,71 @@ def removal_device_bi(cfg):
         _close_conn(cfg, s)
 
 
+def data_send_ipv6_exthdr(cfg):
+    """ Test that IPv6 packets with extension headers are dropped """
+    cfg.require_ipver("6")
+    _init_psp_dev(cfg)
+
+    s = _make_psp_conn(cfg, 0, "6")
+
+    rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
+                                   "dev-id": cfg.psp_dev_id,
+                                   "sock-fd": s.fileno()})
+    tx = _spi_xchg(s, rx_assoc['rx-key'])
+    cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
+                        "version": 0,
+                        "tx-key": tx,
+                        "sock-fd": s.fileno()})
+    _check_data_outq(s, 0)
+
+    def wait_outq_empty():
+        for _ in range(100):
+            if _get_outq(s) == 0:
+                break
+            time.sleep(0.5)
+        _check_data_outq(s, 0)
+
+    # 1. No options: sent and counted
+    tx_pkts = _get_quiesced_stat(cfg, 'tx-packets')
+    total = _send_careful(cfg, s, 20)
+    wait_outq_empty()
+    ksft_gt(_get_quiesced_stat(cfg, 'tx-packets'), tx_pkts)
+    _check_data_rx(cfg, total)
+
+    # In happy path, PSP traffic arrives at the remote decrypted as TCP
+    remote_udp = _remote_udp_from(cfg, s.getsockname()[0],
+                                  s.getpeername()[0])
+
+    # An extension header with one PadN option, as destination options and
+    # as hop-by-hop options
+    for name, opt in (("IPV6_DSTOPTS", socket.IPV6_DSTOPTS),
+                      ("IPV6_HOPOPTS", socket.IPV6_HOPOPTS)):
+        # 2. With the option: dropped, not counted, and nothing reaches
+        # the peer. The data stays queued.
+        s.setsockopt(socket.IPPROTO_IPV6, opt,
+                     bytes([0, 0, 1, 4, 0, 0, 0, 0]))
+        tx_pkts = _get_quiesced_stat(cfg, 'tx-packets')
+        udp = remote_udp() if remote_udp else 0
+        queued = _send_careful(cfg, s, 20)
+        _check_data_outq(s, queued, force_wait=True)
+        ksft_eq(_get_quiesced_stat(cfg, 'tx-packets'), tx_pkts, comment=name)
+        if remote_udp:
+            ksft_eq(remote_udp(), udp, comment=f"{name}: UDP at the remote")
+        _check_data_rx(cfg, total)
+        total += queued
+
+        # 3. Option removed: the queued data, then new data, sent and
+        # counted
+        tx_pkts = _get_quiesced_stat(cfg, 'tx-packets')
+        s.setsockopt(socket.IPPROTO_IPV6, opt, b'')
+        wait_outq_empty()
+        total += _send_careful(cfg, s, 20)
+        wait_outq_empty()
+        ksft_gt(_get_quiesced_stat(cfg, 'tx-packets'), tx_pkts, comment=name)
+        _check_data_rx(cfg, total)
+    _close_psp_conn(cfg, s)
+
+
 def _get_psp_ver_ip_variants():
     for ver in range(4):
         for ipv in ("4", "6"):
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-10-21 21:02 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 21:01 [PATCH net 0/2] psp: drop packets with extension headers in transport mode Todd Freed
2026-10-21 21:01 ` [PATCH net 1/2] psp: drop transport mode packets with extension headers Todd Freed
2026-10-25 21:30   ` netdev-bot+sashiko
2026-10-21 21:02 ` Todd Freed [this message]
2026-10-25 21:30   ` [PATCH net 2/2] selftests: drv-net: psp: test ipv6 tcp socket " netdev-bot+sashiko
2026-10-21 22:07 ` [PATCH net 0/2] psp: drop packets with extension headers in transport mode Daniel Zahka
2026-10-21 23:16   ` Todd Bert Freed
2026-10-22 11:59     ` Björn Töpel
2026-10-22 12:06     ` Daniel Zahka
2026-10-22 22:43       ` Todd Bert Freed
2026-10-22 15:19 ` Willem de Bruijn
2026-10-22 16:58   ` Todd Bert Freed
2026-10-22 20:35     ` Willem de Bruijn

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=20261021210200.788191-3-toddfreed@meta.com \
    --to=toddfreed@meta.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=cratiu@nvidia.com \
    --cc=daniel.zahka@gmail.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=raeds@nvidia.com \
    --cc=rrameshbabu@nvidia.com \
    --cc=shuah@kernel.org \
    --cc=willemdebruijn.kernel@gmail.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