From: Todd Freed <toddfreed@meta.com>
To: <netdev@vger.kernel.org>, <toddfreed@meta.com>
Cc: "David S. Miller" <davem@davemloft.net>,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"Björn Töpel" <bjorn@kernel.org>,
"Boris Pismenny" <borisp@nvidia.com>,
"Cosmin Ratiu" <cratiu@nvidia.com>,
"Daniel Zahka" <daniel.zahka@gmail.com>,
"David Ahern" <dsahern@kernel.org>,
"Dragos Tatulea" <dtatulea@nvidia.com>,
"Eric Dumazet" <edumazet@kernel.org>,
"Ido Schimmel" <idosch@nvidia.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"John Fastabend" <john.fastabend@gmail.com>,
"Leon Romanovsky" <leon@kernel.org>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-rdma@vger.kernel.org, "Mark Bloch" <mbloch@nvidia.com>,
"Paolo Abeni" <pabeni@redhat.com>,
"Raed Salem" <raeds@nvidia.com>,
"Rahul Rameshbabu" <rrameshbabu@nvidia.com>,
"Sabrina Dubroca" <sd@queasysnail.net>,
"Saeed Mahameed" <saeedm@nvidia.com>,
"Shuah Khan" <shuah@kernel.org>,
"Simon Horman" <horms@kernel.org>,
"Tariq Toukan" <tariqt@nvidia.com>,
"Wei Wang" <weiwan@google.com>,
"Willem de Bruijn" <willemdebruijn.kernel@gmail.com>
Subject: [PATCH net v5 6/8] selftests: drv-net: psp: test ipv6 tcp socket with extension headers
Date: Tue, 10 Nov 2026 16:18:29 -0800 [thread overview]
Message-ID: <20261111001831.3710881-7-toddfreed@meta.com> (raw)
In-Reply-To: <20261111001831.3710881-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, counted, and received.
2/ an extension header set on the socket. the packets are dropped,
tx-packets is unchanged, and nothing reaches the peer. the data
stays queued.
3/ option removed. the queued data, then new data, is sent, counted,
and received.
the test has a variant for each extension header a socket can set: a
destination options header (IPV6_DSTOPTS) and a hop-by-hop options
header (IPV6_HOPOPTS), each holding one padn option, and a segment
routing header (IPV6_RTHDR).
a software filter on the peer also counts, and drops, udp packets from
the socket's address to the peer's. psp packets reach the peer as
decrypted tcp packets, as expected. packets which were encapsulated but
are malformed or unencrypted (this is what mlx5 does) show up as udp,
and the test asserts there are none. if the filter cannot be set up the
test is skipped.
tx-packets is read after it has quiesced, because some devices refresh
counters periodically (mlx5 takes about a second).
without the fix in "psp: don't encapsulate ipv6 packets with extension
headers", netdevsim encapsulates the packets in a malformed way and
counts them 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 | 148 ++++++++++++++++++++-
2 files changed, 146 insertions(+), 3 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 473500901879..7e251439a2fd 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -18,12 +18,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
@@ -133,6 +133,58 @@ 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_count(cfg, proto, match):
+ """
+ Count, and drop, the @proto packets matching flower @match which reach
+ the remote, with a software filter on its ingress (offloaded filter
+ counters are refreshed periodically). Dropping keeps such packets away
+ from the remote's stack. Returns a function reading the count, or None
+ if the filter can't be installed.
+ """
+ flt = (f"dev {cfg.remote_ifname} ingress protocol {proto} pref 1 "
+ f"handle 0x5053 flower")
+ try:
+ qdiscs = tc(f"qdisc show dev {cfg.remote_ifname}", json=True,
+ host=cfg.remote)
+ # An ingress qdisc takes the filter as well as clsact
+ if not any(q['kind'] in ('clsact', 'ingress') 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 {match} action drop", host=cfg.remote)
+ except CmdExitFailure as e:
+ ksft_pr(f"Can't install a filter 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
#
@@ -658,6 +710,95 @@ def removal_device_bi(cfg):
_close_conn(cfg, s)
+def _ipv6_exthdr_variants():
+ padn = bytes([0, 0, 1, 4, 0, 0, 0, 0]) # one PadN option
+
+ def srh(peer):
+ """ segment routing header, with @peer as its only segment """
+ return (bytes([0, 2, 4, 0, 0, 0, 0, 0]) +
+ socket.inet_pton(socket.AF_INET6, peer))
+
+ yield KsftNamedVariant("dstopts", socket.IPV6_DSTOPTS, lambda peer: padn)
+ yield KsftNamedVariant("hopopts", socket.IPV6_HOPOPTS, lambda peer: padn)
+ yield KsftNamedVariant("rthdr", socket.IPV6_RTHDR, srh)
+
+
+def _src_addr(dst):
+ """ The source address the kernel picks for @dst """
+ with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as u:
+ u.connect((dst, 9))
+ return u.getsockname()[0]
+
+
+@ksft_variants(_ipv6_exthdr_variants())
+def data_send_ipv6_exthdr(cfg, opt, val):
+ """ Test that PSP drops IPv6 packets with an extension header """
+ cfg.require_ipver("6")
+ _init_psp_dev(cfg)
+
+ # Packets which were encapsulated but malformed or left unencrypted
+ # reach the remote as UDP. Set up the count before connecting, so a
+ # skip doesn't leave the responder mid key exchange.
+ peer = cfg.remote_addr_v["6"]
+ remote_udp = _remote_count(cfg, "ipv6", f"src_ip {_src_addr(peer)} "
+ f"dst_ip {peer} ip_proto udp")
+ if not remote_udp:
+ raise KsftSkipEx("can't count UDP on the remote")
+
+ s = _make_psp_conn(cfg, 0, "6")
+ try:
+ 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 option: sent, counted, and received
+ 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)
+
+ # 2. With the option: dropped, not counted, and nothing reaches the
+ # peer. The data stays queued.
+ s.setsockopt(socket.IPPROTO_IPV6, opt, val(peer))
+ tx_pkts = _get_quiesced_stat(cfg, 'tx-packets')
+ udp = remote_udp()
+ queued = _send_careful(cfg, s, 20)
+ _check_data_outq(s, queued, force_wait=True)
+ ksft_eq(_get_quiesced_stat(cfg, 'tx-packets'), tx_pkts)
+ ksft_eq(remote_udp(), udp, comment="UDP at the remote")
+ _check_data_rx(cfg, total)
+ total += queued
+
+ # 3. Option removed: the queued data, then new data, sent and
+ # received
+ 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)
+ _check_data_rx(cfg, total)
+ finally:
+ # With the option, the queued data, the FIN and the ACKs would be
+ # dropped, leaving an orphan holding the association
+ s.setsockopt(socket.IPPROTO_IPV6, opt, b'')
+ _close_psp_conn(cfg, s)
+
+
def _get_psp_ver_ip_variants():
for ver in range(4):
for ipv in ("4", "6"):
@@ -1173,7 +1314,8 @@ def main() -> None:
cfg.comm_port),
timeout=1)
- cases = [data_basic_send, data_mss_adjust]
+ cases = [data_basic_send, data_mss_adjust,
+ data_send_ipv6_exthdr]
if has_cont:
cases += [
--
2.53.0-Meta
next prev parent reply other threads:[~2026-11-11 0:19 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
2026-11-11 0:18 ` [PATCH net v5 1/8] net/mlx5e: ring the tx doorbell when an accel offload drops a packet Todd Freed
2026-11-11 0:18 ` [PATCH net v5 2/8] psp: don't encapsulate ipv6 packets with extension headers Todd Freed
2026-11-11 0:18 ` [PATCH net v5 3/8] net: don't fragment decrypted skbs Todd Freed
2026-11-11 0:18 ` [PATCH net v5 4/8] ipv4: icmp: don't quote decrypted payload in icmp errors Todd Freed
2026-11-11 0:18 ` [PATCH net v5 5/8] ipv6: icmpv6: don't quote decrypted payload in icmpv6 errors Todd Freed
2026-11-11 0:18 ` Todd Freed [this message]
2026-11-11 0:18 ` [PATCH net v5 7/8] selftests: drv-net: psp: test that ipv4 psp packets aren't fragmented Todd Freed
2026-11-11 0:18 ` [PATCH net v5 8/8] net/mlx5e: psp: tso: replace only the length in checksum seed Todd Freed
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=20261111001831.3710881-7-toddfreed@meta.com \
--to=toddfreed@meta.com \
--cc=andrew+netdev@lunn.ch \
--cc=bjorn@kernel.org \
--cc=borisp@nvidia.com \
--cc=cratiu@nvidia.com \
--cc=daniel.zahka@gmail.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=dtatulea@nvidia.com \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=mbloch@nvidia.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=raeds@nvidia.com \
--cc=rrameshbabu@nvidia.com \
--cc=saeedm@nvidia.com \
--cc=sd@queasysnail.net \
--cc=shuah@kernel.org \
--cc=tariqt@nvidia.com \
--cc=weiwan@google.com \
--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
all inboxes | Powered by JetHome®