mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext
@ 2026-11-11  0:18 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
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

While working on adding support for socket-less psp associations, I
noticed the case of ipv6 extension headers was not being handled
properly for psp packets. It will be relevant with that series, because
it adds udp support, for which the psp nexthdr needs to look at the
inner packet.

That led me to various other ways where packets on the crypto offload
path could be transmitted unencrypted.

Since the psp/tls association encodes intent to send encrypted packets,
but these various cases circumvent that, sending these as fixes.

The cases are,

 1/ ipv6 extension headers (patch 2; patch 1 makes mlx5e flush its tx
    queue when the resulting drop happens),
 2/ fragments (patch 3),
 3/ icmp errors (patch 4),
 4/ icmpv6 errors (patch 5),

Cases 2, 3 and 4 are relevant for ktls as well as psp.

patch 8 fixes an mlx5e bug in tso checksum calculation for psp packets
when the l3 dest is an intermediate hop (ipv4 source routing). It's not
about preventing unencrypted tx, so could be considered separate.

netfilter refragments bridged packets and builds reject replies itself,
outside these paths. A separate series to nf-next, which follows this
one, applies the same rules there.

Testing:

all hardware testing was done on connectx-7 (fw 28.48), between two x86
hosts, plus netdevsim.

 - drivers/net psp.py: on netdevsim, all 36 tests pass. on mlx5, 30
   pass, 4 skip (gmac versions the device doesn't support), and 2 xfail
   (netdevsim only).

 - tso checksum seed: a psp tcp socket with an ipv4 loose source route,
   the peer checking tcp checksums in software. hundreds of checksum
   errors before the fix, none after.

 - fragments, with tls offload and psp:
   - ipv4 (route mtu above the device mtu, IP_PMTUDISC_OMIT): without
     the fix, some fragments after the first go out in cleartext. with
     it they're dropped.
   - ipv6, through an ipvs dr service with a local client: without the
     fix, fragments after the first go out in cleartext, and the peer
     reassembles them. with it they're dropped.

 - icmp and icmpv6 errors, on netdevsim psp, with an nft reject rule
   (icmpx port-unreachable):
   - in an input chain, of received decrypted packets: without the fix,
     the errors go out on the wire, quoting the decrypted packets. with
     it, no error reaches the wire.
   - in an output chain, of a psp socket's packets: the error is
     delivered to the local socket, with and without the fix.
   - a PKT_TOOBIG from ip6_fragment() about a psp socket's packets is
     delivered to the socket, with and without the fix.

v5:
 - new patch 1: mlx5e rings the tx doorbell when an accel offload drops
   a packet, and checks the stop room after the nop it posts (Sashiko)
 - ipv6 extension headers: drop them in psp_dev_encapsulate() again,
   instead of in mlx5e. it's the helper which can't encapsulate them,
   for every driver (netdevsim sent them malformed). unlike the routing
   header rejection in v2, this isn't a device limitation
 - don't fragment decrypted skbs in ip6_fragment() either
 - icmp and icmpv6: send no error at all about a received decrypted skb,
   instead of quoting up to the tcp header for tls offload.
 - icmpv6: decide whether an error about a locally originated skb goes
   to a local address from the error's route (RTF_LOCAL), like icmp
   (RTCF_LOCAL), instead of the address list (Sashiko)
 - selftests: the extension header test runs on netdevsim too, and
   removes the socket option before closing. the fragment test uses the
   source address of the route, and turns off tcp_mtu_probing

v4: https://lore.kernel.org/netdev/20261103001143.246081-1-toddfreed@meta.com
 - back to dropping ipv6 packets with extension headers, but moved into
   mlx5e instead of psp_dev_encapsulate()
 - drop the psp core changes to rx and tx ipv6 extension headers
 - new patch 2 to drop decrypted skbs instead of fragmenting
 - new patches 3 and 4 to prevent quoting just-decrypted packet contents
   in icmp errors, v4 and v6 respectively (Jakub, Bjorn, Sashiko)
 - new selftest: ipv4 fragments of a psp tcp socket

v3: https://lore.kernel.org/netdev/20261023234640.1459291-1-toddfreed@meta.com
 - new patch 1 to fix tso checksum seed calculation in mlx5e (Daniel)
 - accept routing headers and reject ah (Daniel)
 - split the psp_dev_rcv() change into its own patch (Daniel)
 - return -EINVAL instead of -1 (Daniel)
 - selftest: one variant per extension header type (Daniel)

v2: https://lore.kernel.org/netdev/20261022223014.556388-1-toddfreed@meta.com
 - walk the extension headers instead of dropping (Daniel)
 - reject fragment headers (Bjorn, Daniel)
 - reject routing headers
 - psp_dev_rcv() handle the rx case as well

v1: https://lore.kernel.org/netdev/20261021210200.788191-1-toddfreed@meta.com

Todd Freed (8):
  net/mlx5e: ring the tx doorbell when an accel offload drops a packet
  psp: don't encapsulate ipv6 packets with extension headers
  net: don't fragment decrypted skbs
  ipv4: icmp: don't quote decrypted payload in icmp errors
  ipv6: icmpv6: don't quote decrypted payload in icmpv6 errors
  selftests: drv-net: psp: test ipv6 tcp socket with extension headers
  selftests: drv-net: psp: test that ipv4 psp packets aren't fragmented
  net/mlx5e: psp: tso: replace only the length in checksum seed

 .../mellanox/mlx5/core/en_accel/psp_rxtx.c    |  21 +-
 .../net/ethernet/mellanox/mlx5/core/en_tx.c   |   8 +-
 net/ipv4/icmp.c                               |  12 +
 net/ipv4/ip_output.c                          |   8 +
 net/ipv6/icmp.c                               |  16 ++
 net/ipv6/ip6_output.c                         |   9 +
 net/psp/psp_main.c                            |  21 +-
 tools/testing/selftests/drivers/net/config    |   1 +
 tools/testing/selftests/drivers/net/psp.py    | 249 +++++++++++++++++-
 9 files changed, 327 insertions(+), 18 deletions(-)


base-commit: 8012eec76b7e6c0d9fb338fe8e3db6ca1b419da4
-- 
2.53.0-Meta

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 1/8] net/mlx5e: ring the tx doorbell when an accel offload drops a packet
  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 ` Todd Freed
  2026-11-11  0:18 ` [PATCH net v5 2/8] psp: don't encapsulate ipv6 packets with extension headers Todd Freed
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

when mlx5e_accel_tx_begin() drops the skb, e.g. because psp can't
encapsulate it, mlx5e_xmit() returns without posting anything. the skbs
before it in a bulk dequeue may have been posted with xmit_more, which
defers the doorbell to the next one, and an mpwqe session may be open.
the device then doesn't see them until the next transmit on that sq, and
since the queue isn't stopped, the tx watchdog doesn't fire.

flush the sq on this drop like the dma error paths do: close the mpwqe
session, post a nop and ring the doorbell.

mlx5e_tx_flush() doesn't check whether the nop leaves less than
stop_room in the sq. for the dma error paths that is rare, but a run of
offload drops would post nops without stopping the queue, and could
overrun wqes in flight. so check it in mlx5e_tx_flush(), like the other
producers do. the completion of the nop wakes the queue again.

the next patch makes the psp drop reachable from user space, with an
ipv6 extension header set on a psp socket.

Fixes: 2ac9cfe78223 ("net/mlx5e: IPSec, Add Innova IPSec offload TX data path")
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 drivers/net/ethernet/mellanox/mlx5/core/en_tx.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
index 14479da98f42..006bf976def1 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
@@ -365,6 +365,7 @@ static void mlx5e_tx_flush(struct mlx5e_txqsq *sq)
 	};
 
 	wqe = mlx5e_post_nop(&sq->wq, sq->sqn, &sq->pc);
+	mlx5e_tx_check_stop(sq);
 	mlx5e_notify_hw(&sq->wq, sq->pc, sq->uar_map, &wqe->ctrl);
 }
 
@@ -672,8 +673,13 @@ netdev_tx_t mlx5e_xmit(struct sk_buff *skb, struct net_device *dev)
 	}
 
 	/* May send SKBs and WQEs. */
-	if (unlikely(!mlx5e_accel_tx_begin(dev, sq, skb, &accel)))
+	if (unlikely(!mlx5e_accel_tx_begin(dev, sq, skb, &accel))) {
+		/* The skb was dropped: ring the doorbell which the previous
+		 * ones may have deferred to it
+		 */
+		mlx5e_tx_flush(sq);
 		return NETDEV_TX_OK;
+	}
 
 	mlx5e_sq_xmit_prepare(sq, skb, &accel, &attr);
 
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 2/8] psp: don't encapsulate ipv6 packets with extension headers
  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 ` Todd Freed
  2026-11-11  0:18 ` [PATCH net v5 3/8] net: don't fragment decrypted skbs Todd Freed
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

psp_dev_encapsulate() inserts the udp+psp headers at the transport
header, rewrites the protocol of the fixed ip header to udp, and writes
tcp as the psp next header. so tcp must directly follow the fixed
header.

if the packet is an ipv6 packet with extension headers, udp+psp is
placed after them, but the next header indicating the l4 protocol is on
the last extension header in the chain, not in the fixed header.
therefore this produces a malformed packet.

in addition to being malformed, with mlx5 this kind of packet goes out
unencrypted. since the tx association encodes intent to send encrypted
packets, drop such packets instead.

a psp tcp socket emits such packets if certain socket options are set on
it (e.g. IPV6_DSTOPTS). refusing the combination of such socket options
with psp association would be helpful but is not implemented here. since
extension headers can also come from a route the datapath check is
needed anyway.

the check also covers ipv4: the l4 protocol must be tcp, and the
transport header must follow the ip header and its options. packets of
a psp tcp socket always pass it.

Fixes: fc724515741a ("psp: provide encapsulation helper for drivers")
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 net/psp/psp_main.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index 273b010d2355..c071f507261b 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -245,6 +245,24 @@ static void psp_write_headers(struct net *net, struct sk_buff *skb, __be32 spi,
 	memset(&psph->iv, 0, sizeof(psph->iv));
 }
 
+/* UDP+PSP goes in at the transport header, the protocol of the fixed IP
+ * header becomes UDP, and TCP the PSP next header: TCP must directly follow
+ * the fixed header. It doesn't with IPv6 extension headers, whose last one
+ * holds the L4 protocol.
+ */
+static bool psp_dev_encap_ok(const struct sk_buff *skb)
+{
+	u32 network_len = skb_network_header_len(skb);
+
+	if (skb->protocol == htons(ETH_P_IP))
+		return ip_hdr(skb)->protocol == IPPROTO_TCP &&
+		       network_len == ip_hdrlen(skb);
+	if (skb->protocol == htons(ETH_P_IPV6))
+		return ipv6_hdr(skb)->nexthdr == IPPROTO_TCP &&
+		       network_len == sizeof(struct ipv6hdr);
+	return false;
+}
+
 /* Encapsulate a TCP packet with PSP by adding the UDP+PSP headers and filling
  * them in.
  */
@@ -255,8 +273,7 @@ bool psp_dev_encapsulate(struct net *net, struct sk_buff *skb, __be32 spi,
 	u32 ethr_len = skb_mac_header_len(skb);
 	u32 bufflen = ethr_len + network_len;
 
-	if (skb->protocol != htons(ETH_P_IP) &&
-	    skb->protocol != htons(ETH_P_IPV6))
+	if (!psp_dev_encap_ok(skb))
 		return false;
 
 	if (skb_cow_head(skb, PSP_ENCAP_HLEN))
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 3/8] net: don't fragment decrypted skbs
  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 ` Todd Freed
  2026-11-11  0:18 ` [PATCH net v5 4/8] ipv4: icmp: don't quote decrypted payload in icmp errors Todd Freed
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

on transmit, skb->decrypted means the device must encrypt the skb.
if it can't do that, then the packet should be dropped, not transmitted
in the clear.

ip_do_fragment() and ip6_fragment() on a decrypted skb produce
fragments which have skb->decrypted unset, and can be sent out
unencrypted.

tx of individually encrypted fragments would not work anyway, because
encrypting needs the L4 header, which non-initial fragments do not have.

ktls could encrypt in sw before fragmenting. that would not work for psp
offload which does not have a software fallback path.

tcp sizes segments to the mtu, so psp and ktls socket packets normally
won't reach this path. an ipv4 tcp socket can be made to send fragments
by setting a route mtu above the device mtu and disabling pmtud on the
socket (IP_PMTUDISC_OMIT). the selftest later in the series does this.

emitting fragments from an ipv6 tcp socket requires more steps, but is
also possible. the test setup here uses ipvs, which sets ignore_df on
forwarded packets. in ip6_fragment(), check skb->decrypted after the
too-big checks, so that a local socket still gets PKT_TOOBIG for path
mtu discovery.

tested with tls tx offload on mlx5 (connectx-7), over ipv4. without
this patch some fragments after the first go out in cleartext, with it
they're dropped (IpFragFails) and the connection stalls.

tested over ipv6, with tls tx offload and psp on mlx5 (connectx-7),
through an ipvs dr service with a local client. without this patch,
fragments after the first go out in cleartext, and the peer reassembles
them; with it they're dropped (Ip6FragFails).

Fixes: e8f69799810c ("net/tls: Add generic NIC offload infrastructure")
Fixes: 659a2899a57d ("tcp: add datapath logic for PSP with inline key exchange")
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 net/ipv4/ip_output.c  | 8 ++++++++
 net/ipv6/ip6_output.c | 9 +++++++++
 2 files changed, 17 insertions(+)

diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
index d42d776af273..94a40e7ed534 100644
--- a/net/ipv4/ip_output.c
+++ b/net/ipv4/ip_output.c
@@ -770,6 +770,14 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
 	struct ip_frag_state state;
 	int err = 0;
 
+	/* PSP and TLS offload need the packet's L4 header, which non-initial
+	 * fragments do not have.
+	 */
+	if (unlikely(skb_is_decrypted(skb))) {
+		err = -EMSGSIZE;
+		goto fail;
+	}
+
 	/* for offloaded checksums cleanup checksum before fragmentation */
 	if (skb->ip_summed == CHECKSUM_PARTIAL) {
 		err = ip_check_csum_start(skb);
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 25fe0ba98b1a..5f19d170ed29 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -929,6 +929,15 @@ int ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
 			mtu = IPV6_MIN_MTU;
 	}
 
+	/* PSP and TLS offload need the packet's L4 header, which non-initial
+	 * fragments do not have. Checked after the too-big checks above,
+	 * so that PMTUD for a local socket works.
+	 */
+	if (unlikely(skb_is_decrypted(skb))) {
+		err = -EMSGSIZE;
+		goto fail;
+	}
+
 	if (np) {
 		u32 frag_size = READ_ONCE(np->frag_size);
 
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 4/8] ipv4: icmp: don't quote decrypted payload in icmp errors
  2026-11-11  0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
                   ` (2 preceding siblings ...)
  2026-11-11  0:18 ` [PATCH net v5 3/8] net: don't fragment decrypted skbs Todd Freed
@ 2026-11-11  0:18 ` Todd Freed
  2026-11-11  0:18 ` [PATCH net v5 5/8] ipv6: icmpv6: don't quote decrypted payload in icmpv6 errors Todd Freed
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn, Sashiko

an icmp error quotes as much of the invoking packet as fits in 576
bytes. if the device had decrypted the packet (as indicated by
skb->decrypted) then the icmp error is sending part of it back out onto
the network in cleartext.

the two offloads encrypt different parts of the packet:

- ktls encrypts the tcp payload. the tcp header is in the clear on the
  wire, so a quote of the ip header and the tcp header, without the
  payload, would be fine.

- psp encryption also covers the tcp header. the quote could include
  just the ip header, but then that's not really useful. linux receivers
  want to look at the l4 layer on the quoted packet to look up the
  related flow, so such a truncated icmp error packet is just dropped
  (icmp_socket_deliver()).

the two offloads can't be told apart reliably. a decrypted psp skb has
SKB_EXT_PSP, but some paths drop it and keep skb->decrypted
(skb_scrub_packet(), e.g. on bpf redirects and tunnel decap). so don't
send an error about a received decrypted skb at all.

on tx, skb->decrypted is also set. an error about a locally originated
skb (skb_iif is 0) is sent in full as before, if it is routed to a local
address (RTCF_LOCAL on the route to the packet's source address).

a reject rule (iptables REJECT, nft reject) sends its icmp error with
icmp_send(), through nf_send_unreach(). so now, a received packet which
the device decrypted, and which hits a reject rule in the INPUT or
FORWARD chain, is now dropped without a reply, like a drop rule.

packets which weren't decrypted are rejected as before, and so are
locally originated ones in the OUTPUT chain, if the error goes to a
local address as above.

the next patch does the same for icmpv6.

Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261023234640.1459291-1-toddfreed%40meta.com
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Suggested-by: Björn Töpel <bjorn@kernel.org>
Fixes: 784abe24c903 ("net: Add decrypted field to skb")
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 net/ipv4/icmp.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 90c0e22c29be..e11008aa4cec 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -979,6 +979,18 @@ void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
 	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
 		goto ende;
 
+	/* Don't send parts of a decrypted skb onto the network. A received skb
+	 * was decrypted by the device, and the transport header may have been
+	 * encrypted. The different offloads can't be distinguished in all cases
+	 * so simply don't send the error.
+	 *
+	 * A locally originated skb to be encrypted may be quoted if its source
+	 * address (target of the error) is local.
+	 */
+	if (unlikely(skb_is_decrypted(skb_in)) &&
+	    (skb_in->skb_iif || !(rt->rt_flags & RTCF_LOCAL)))
+		goto ende;
+
 	/* peer icmp_ratelimit */
 	if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code, apply_ratelimit))
 		goto ende;
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 5/8] ipv6: icmpv6: don't quote decrypted payload in icmpv6 errors
  2026-11-11  0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
                   ` (3 preceding siblings ...)
  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 ` Todd Freed
  2026-11-11  0:18 ` [PATCH net v5 6/8] selftests: drv-net: psp: test ipv6 tcp socket with extension headers Todd Freed
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn, Sashiko

an icmpv6 error quotes as much of the invoking packet as fits in the
minimum ipv6 mtu. if the device had decrypted the packet (as indicated
by skb->decrypted) then the icmpv6 error is sending part of it back out
onto the network in cleartext.

as in the previous patch for icmp: ktls rx device offload and psp can't
be told apart reliably, and psp also encrypts the l4 header, so don't
send an error about a received decrypted skb at all. linux receivers
would drop an error whose quote stops before the l4 header anyway
(icmpv6_notify()).

on tx, skb->decrypted is also set. an error about a locally originated
skb (skb_iif is 0) is sent in full as before, if its route goes to a
local address (RTF_LOCAL), like RTCF_LOCAL in the previous patch.

an error about a received decrypted skb is dropped before the rate
limit, so that it doesn't use up the peer's and the global icmpv6
credit.

Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261023234640.1459291-1-toddfreed%40meta.com
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Suggested-by: Björn Töpel <bjorn@kernel.org>
Fixes: 784abe24c903 ("net: Add decrypted field to skb")
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 net/ipv6/icmp.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c
index d8203ebad882..949025d83e15 100644
--- a/net/ipv6/icmp.c
+++ b/net/ipv6/icmp.c
@@ -778,6 +778,13 @@ void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
 
 	np = inet6_sk(sk);
 
+	/* Don't send parts of a decrypted skb onto the network, see
+	 * __icmp_send(): no error about a received skb. Before the rate
+	 * limit, so that a suppressed error costs no credit.
+	 */
+	if (unlikely(skb_is_decrypted(skb) && skb->skb_iif))
+		goto out_unlock;
+
 	if (!icmpv6_xrlim_allow(sk, type, &fl6, apply_ratelimit))
 		goto out_unlock;
 
@@ -799,6 +806,15 @@ void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
 	if (IS_ERR(dst))
 		goto out_unlock;
 
+	/* An error about a locally originated decrypted skb is sent only if
+	 * it is routed to a local address.
+	 */
+	if (unlikely(skb_is_decrypted(skb)) &&
+	    !(dst_rt6_info(dst)->rt6i_flags & RTF_LOCAL)) {
+		dst_release(dst);
+		goto out_unlock;
+	}
+
 	ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst);
 
 	msg.skb = skb;
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 6/8] selftests: drv-net: psp: test ipv6 tcp socket with extension headers
  2026-11-11  0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
                   ` (4 preceding siblings ...)
  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
  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
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

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


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 7/8] selftests: drv-net: psp: test that ipv4 psp packets aren't fragmented
  2026-11-11  0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
                   ` (5 preceding siblings ...)
  2026-11-11  0:18 ` [PATCH net v5 6/8] selftests: drv-net: psp: test ipv6 tcp socket with extension headers Todd Freed
@ 2026-11-11  0:18 ` 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
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

make the ipv4 packets of a psp tcp socket need fragmenting, and check
that they're dropped (IpFragFails) and not transmitted unencrypted.

tcp sizes its segments from the route mtu. with IP_PMTUDISC_OMIT, ip
fragments them to the device mtu, and doesn't set df. so the test sets a
route mtu of twice the larger of the two device mtus (at most 65535) on
both ends (the peer's mss limits ours too), keeping the device and
source address of the current route, and IP_PMTUDISC_OMIT on the
socket. tcp mtu probing (tcp_mtu_probing 2) would cap the mss at the
probing base, so the test turns it off. if the peer isn't on-link, or
the socket's mss doesn't need fragmenting, the test is skipped.

a software filter on the peer counts, and drops, the ipv4 fragments
which reach it from the socket's address, and the test asserts there are
none. IpFragFails is per netns, so it only confirms that the packets
were dropped.

while both route mtus are in place, a new ssh connection to the remote
over these addresses can stall: its segments are sized from the route
mtu, and sent with df set. so the local route is added last, and removed
before the next remote command.

no ipv6 test: the stack only fragments ipv6 tcp packets when something
like ipvs sets ignore_df on them (see "net: don't fragment decrypted
skbs"), which this test doesn't set up.

without the fix in "net: don't fragment decrypted skbs", the packets are
fragmented, and the fragments go out unencrypted. they reach the peer,
IpFragFails doesn't move, and the test fails.

Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 tools/testing/selftests/drivers/net/psp.py | 101 +++++++++++++++++++++
 1 file changed, 101 insertions(+)

diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index 7e251439a2fd..5dac62ad96b0 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -26,6 +26,8 @@ from lib.py import bkg, rand_port, wait_port_listen
 from lib.py import ip, tc
 
 TCP_ULP = 31
+IP_MTU_DISCOVER = 10
+IP_PMTUDISC_OMIT = 5
 
 
 def _get_outq(s):
@@ -799,6 +801,105 @@ def data_send_ipv6_exthdr(cfg, opt, val):
         _close_psp_conn(cfg, s)
 
 
+def _ipv4_snmp(key):
+    """ An Ip: counter of this netns, from /proc/net/snmp """
+    with open("/proc/net/snmp", encoding="utf-8") as f:
+        ip_lines = [line.split() for line in f if line.startswith("Ip:")]
+    return int(ip_lines[1][ip_lines[0].index(key)])
+
+
+def _ipv4_route_mtu(dst, mtu, host=None):
+    """ Add a route to @dst with @mtu, keeping the device and source of
+    the current one, which must be on-link. Returns the defer removing it.
+    """
+    route = ip(f"-4 route get {dst}", json=True, host=host)[0]
+    if route.get("gateway"):
+        raise KsftSkipEx(f"{dst} isn't on-link")
+    try:
+        ip(f"route add {dst}/32 dev {route['dev']} src {route['prefsrc']} "
+           f"mtu {mtu}", host=host)
+    except CmdExitFailure as e:
+        raise KsftSkipEx(f"can't set the route mtu to {dst}: {e}") from e
+    return defer(ip, f"route del {dst}/32 dev {route['dev']}", host=host)
+
+
+def data_send_ipv4_frags(cfg):
+    """ Test that IPv4 packets of a PSP TCP socket aren't fragmented """
+    cfg.require_ipver("4")
+    _init_psp_dev(cfg)
+
+    # Fragments which get out reach the remote. Set up the count before
+    # connecting, so a skip doesn't leave the responder mid key exchange.
+    # The socket's source is the one the kernel picks, which may not be
+    # LOCAL_V4, and the fragments would leave through the route's device
+    rem = cfg.remote_addr_v["4"]
+    route = ip(f"-4 route get {rem}", json=True)[0]
+    if route["dev"] != cfg.ifname:
+        raise KsftSkipEx(f"{rem} isn't routed through {cfg.ifname}")
+    loc = route["prefsrc"]
+    remote_frags = _remote_count(cfg, "ip",
+                                 f"src_ip {loc} dst_ip {rem} ip_flags frag")
+    if not remote_frags:
+        raise KsftSkipEx("can't count fragments on the remote")
+
+    # TCP sizes its segments from the route MTU. With IP_PMTUDISC_OMIT, IP
+    # fragments them to the device MTU. Set a route MTU above the device
+    # MTU on both ends, since the peer's MSS limits ours too. While both
+    # are in place, a new ssh connection to the remote over these addresses
+    # can stall, so add the local one last, and remove it first.
+    mtu = min(2 * max(cfg.dev['mtu'], cfg.remote_dev['mtu']), 65535)
+    _ipv4_route_mtu(loc, mtu, host=cfg.remote)
+    local_route = _ipv4_route_mtu(rem, mtu)
+
+    # With MTU probing on, TCP caps the MSS at the probing base, whatever
+    # the route MTU
+    probing_path = "/proc/sys/net/ipv4/tcp_mtu_probing"
+    with open(probing_path, encoding="utf-8") as fp:
+        probing = fp.read().strip()
+    if int(probing) > 1:
+        def set_probing(val):
+            with open(probing_path, "w", encoding="utf-8") as fp:
+                fp.write(val)
+        set_probing("0")
+        defer(set_probing, probing)
+
+    s = _make_psp_conn(cfg, 0, "4")
+    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()})
+        s.setsockopt(socket.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_OMIT)
+        mss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
+        if mss + 40 <= cfg.dev['mtu']:
+            raise KsftSkipEx(f"mss {mss} needs no fragmenting")
+
+        fails = _ipv4_snmp("FragFails")
+        # The packets are dropped, so the send may not complete (with a
+        # jumbo device MTU, the socket buffer can take all of it)
+        s.settimeout(3)
+        try:
+            s.sendall(b'f' * 200000)
+        except socket.timeout:
+            pass
+        fails = _ipv4_snmp("FragFails") - fails
+
+        # Before the next ssh to the remote, see above
+        local_route.exec()
+
+        # Fragments would go out unencrypted: the packets must be dropped
+        # instead of fragmented. FragFails is per netns, so it only
+        # confirms the drop: the remote count is what decides.
+        ksft_eq(remote_frags(), 0, comment="fragments reached the remote")
+        ksft_gt(fails, 0, comment="packets dropped instead of fragmented")
+    finally:
+        _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


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH net v5 8/8] net/mlx5e: psp: tso: replace only the length in checksum seed
  2026-11-11  0:18 [PATCH net v5 0/8] psp, tls: don't send offloaded contents in cleartext Todd Freed
                   ` (6 preceding siblings ...)
  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 ` Todd Freed
  7 siblings, 0 replies; 9+ messages in thread
From: Todd Freed @ 2026-11-11  0:18 UTC (permalink / raw)
  To: netdev, toddfreed
  Cc: David S. Miller, Andrew Lunn, Björn Töpel,
	Boris Pismenny, Cosmin Ratiu, Daniel Zahka, David Ahern,
	Dragos Tatulea, Eric Dumazet, Ido Schimmel, Jakub Kicinski,
	John Fastabend, Leon Romanovsky, linux-kernel, linux-kselftest,
	linux-rdma, Mark Bloch, Paolo Abeni, Raed Salem,
	Rahul Rameshbabu, Sabrina Dubroca, Saeed Mahameed, Shuah Khan,
	Simon Horman, Tariq Toukan, Wei Wang, Willem de Bruijn

for psp tcp with tso,

 - __tcp_transmit_skb() -> tcp_v6_send_check() or tcp_v4_send_check()
   computes the checksum seed using the addresses on the socket and the
   total length, then

 - mlx5e_psp_handle_tx_skb() recomputes the checksum seed using
   addresses from the packet's l3 header, and the length of one segment

the length difference is expected by the device. however, the difference
in addresses is incorrect in cases where the l3 header daddr is an
intermediate hop, for example,

 - ipv4 source route option (IP_OPTIONS with lsrr or ssrr)
 - ipv6 IPV6_RTHDR header (conceptually ; ipv6 extension headers are not
   properly supported on this path yet)

in these cases every segment of the tso packet to go out with a bad tcp
checksum

the fix is to replace only the length in the seed which tcp had already
computed. this is also what tcp_gso_segment() does.

tested on connectx-7, with a psp tcp socket whose ipv4 loose source
route's first hop isn't the final destination, and the peer checking
tcp checksums in software.

Fixes: e5a1861a298e ("net/mlx5e: Implement PSP Tx data path")
Acked-by: Daniel Zahka <daniel.zahka@gmail.com>
Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 .../mellanox/mlx5/core/en_accel/psp_rxtx.c    | 21 ++++++++-----------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
index 348fd7a96261..d9c2742b3c24 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/psp_rxtx.c
@@ -177,18 +177,15 @@ bool mlx5e_psp_handle_tx_skb(struct net_device *netdev,
 		return false;
 	}
 	if (skb_is_gso(skb)) {
-		int len = skb_shinfo(skb)->gso_size + inner_tcp_hdrlen(skb);
-		struct tcphdr *th = inner_tcp_hdr(skb);
-
-		if (skb->protocol == htons(ETH_P_IP)) {
-			const struct iphdr *ip = ip_hdr(skb);
-
-			th->check = ~tcp_v4_check(len, ip->saddr, ip->daddr, 0);
-		} else {
-			const struct ipv6hdr *ip6 = ipv6_hdr(skb);
-
-			th->check = ~tcp_v6_check(len, &ip6->saddr, &ip6->daddr, 0);
-		}
+		u32 seg_len = skb_shinfo(skb)->gso_size + inner_tcp_hdrlen(skb);
+		u32 len = skb->len - skb_inner_transport_offset(skb);
+
+		/* update just the length component of the checksum seed
+		 * tcp computed the seed with the total length, but mlx5
+		 * devices are expecting the segment length
+		 */
+		inet_proto_csum_replace4(&inner_tcp_hdr(skb)->check, skb,
+					 htonl(len), htonl(seg_len), true);
 	}
 
 	return true;
-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-11-11  0:19 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH net v5 6/8] selftests: drv-net: psp: test ipv6 tcp socket with extension headers Todd Freed
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

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®