Netdev List
 help / color / mirror / Atom feed
From: Chuck Lever <cel@kernel.org>
To: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
	 Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <Dai.Ngo@oracle.com>,  Tom Talpey <tom@talpey.com>
Cc: Rick Macklem <rmacklem@uoguelph.ca>,
	linux-nfs@vger.kernel.org,  netdev@vger.kernel.org,
	Chuck Lever <cel@kernel.org>
Subject: [PATCH v8 14/14] SUNRPC: Advance the acked position on TLS connections
Date: Wed, 04 Nov 2026 09:11:14 -0500	[thread overview]
Message-ID: <20261104-duplicate-reply-cache-v8-14-eea5b90e4318@kernel.org> (raw)
In-Reply-To: <20261104-duplicate-reply-cache-v8-0-eea5b90e4318@kernel.org>

The DRC retires an entry once its reply lies at or below the
transport's xpt_acked_pos, the position svc_tcp_update_acked()
advances as TCP acknowledgments arrive. When XPT_TLS_SESSION is set,
svc_tcp_update_acked() leaves xpt_acked_pos alone, because kTLS can
return a full count from sendmsg() while a record is still waiting to
reach TCP. DRC entries on TLS connections therefore never retire by
acknowledgment.

Advance xpt_acked_pos on a TLS connection when no record is open,
awaiting encryption, or parked on a short TCP send. Every field the
check reads is declared in include/net/tls.h, so the predicate lives
beside its one caller rather than in net/tls. After the change,
a TLS connection takes the socket lock once per reply, as a plain
TCP connection already does. Record in svcsock_tcp_acked why a reply
published no position.

On the receive side, copied_seq does not track plaintext under kTLS.
tls_strp_msg_consume() advances copied_seq by the whole ciphertext
record once the record is decrypted, before svc_tcp_recv_actor()
sees any of its plaintext, and in copy mode the strparser advances
copied_seq while pulling the ciphertext in. A record that spans the
end of one call and the start of a retransmit leaves the
retransmit's plaintext on rx_list, uncounted in xpt_recv_pending,
with copied_seq already at the mark. Withhold the position while
sk_is_readable() reports plaintext on rx_list or a record ready in
the strparser. A record the strparser is still copying in is neither
readable nor ready, so withhold the position while the strparser's
copy_mode is set as well. The strparser fields are declared in
include/net/tls.h with the socket lock named as their guard.

The close_notify alert and the FIN sent at detach go out without
xpt_mutex. Both are non-blocking and never drop the socket lock
mid-push, so their bytes either reach write_seq and lower the position,
or park and hold it.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 include/linux/sunrpc/svcsock.h |   9 ++++
 include/trace/events/sunrpc.h  |  23 +++++++--
 net/sunrpc/svcsock.c           | 111 +++++++++++++++++++++++++++++++++++------
 3 files changed, 125 insertions(+), 18 deletions(-)

diff --git a/include/linux/sunrpc/svcsock.h b/include/linux/sunrpc/svcsock.h
index 0fa62f9c6465..bd5cd79c1870 100644
--- a/include/linux/sunrpc/svcsock.h
+++ b/include/linux/sunrpc/svcsock.h
@@ -49,12 +49,21 @@ struct svc_sock {
 	u32			sk_acked_rcv;
 
 	struct completion	sk_handshake_done;
+	/* tls ULP installed, recorded once; the ULP has no removal path */
+	bool			sk_tls_ulp;
 
 	/* received data */
 	unsigned long		sk_maxpages;
 	struct page *		sk_pages[] __counted_by(sk_maxpages);
 };
 
+/* Why svc_tcp_update_acked() did or did not publish a position */
+enum svc_tcp_acked_reason {
+	SVC_TCP_ACKED_PUBLISHED,
+	SVC_TCP_ACKED_NO_ULP,
+	SVC_TCP_ACKED_TX_PENDING,
+};
+
 static inline u32 svc_sock_reclen(struct svc_sock *svsk)
 {
 	return be32_to_cpu(svsk->sk_marker) & RPC_FRAGMENT_SIZE_MASK;
diff --git a/include/trace/events/sunrpc.h b/include/trace/events/sunrpc.h
index a5d914376627..a7e96e49e0d0 100644
--- a/include/trace/events/sunrpc.h
+++ b/include/trace/events/sunrpc.h
@@ -10,6 +10,7 @@
 #include <linux/sunrpc/svc.h>
 #include <linux/sunrpc/xprtsock.h>
 #include <linux/sunrpc/svc_xprt.h>
+#include <linux/sunrpc/svcsock.h>
 #include <net/tcp_states.h>
 #include <linux/net.h>
 #include <linux/tracepoint.h>
@@ -2349,6 +2350,16 @@ TRACE_EVENT(svcsock_tcp_recv_short,
 	)
 );
 
+TRACE_DEFINE_ENUM(SVC_TCP_ACKED_PUBLISHED);
+TRACE_DEFINE_ENUM(SVC_TCP_ACKED_NO_ULP);
+TRACE_DEFINE_ENUM(SVC_TCP_ACKED_TX_PENDING);
+
+#define show_svc_tcp_acked_reason(reason)				\
+	__print_symbolic(reason,					\
+		{ SVC_TCP_ACKED_PUBLISHED,	"published" },		\
+		{ SVC_TCP_ACKED_NO_ULP,		"no_ulp" },		\
+		{ SVC_TCP_ACKED_TX_PENDING,	"tx_pending" })
+
 TRACE_EVENT(svcsock_tcp_acked,
 	TP_PROTO(
 		const struct svc_xprt *xprt,
@@ -2356,10 +2367,12 @@ TRACE_EVENT(svcsock_tcp_acked,
 		u32 write_seq,
 		u32 snd_una,
 		u64 candidate,
-		u64 acked_pos
+		u64 acked_pos,
+		enum svc_tcp_acked_reason reason
 	),
 
-	TP_ARGS(xprt, send_pos, write_seq, snd_una, candidate, acked_pos),
+	TP_ARGS(xprt, send_pos, write_seq, snd_una, candidate, acked_pos,
+		reason),
 
 	TP_STRUCT__entry(
 		__field(u64, xpt_id)
@@ -2368,6 +2381,7 @@ TRACE_EVENT(svcsock_tcp_acked,
 		__field(u64, acked_pos)
 		__field(u32, write_seq)
 		__field(u32, snd_una)
+		__field(unsigned int, reason)
 		__string(addr, xprt->xpt_remotebuf)
 	),
 
@@ -2378,13 +2392,14 @@ TRACE_EVENT(svcsock_tcp_acked,
 		__entry->acked_pos = acked_pos;
 		__entry->write_seq = write_seq;
 		__entry->snd_una = snd_una;
+		__entry->reason = reason;
 		__assign_str(addr);
 	),
 
-	TP_printk("addr=%s xpt_id=%llu send_pos=%llu write_seq=%u snd_una=%u candidate=%llu acked_pos=%llu",
+	TP_printk("addr=%s xpt_id=%llu send_pos=%llu write_seq=%u snd_una=%u candidate=%llu acked_pos=%llu reason=%s",
 		__get_str(addr), __entry->xpt_id, __entry->send_pos,
 		__entry->write_seq, __entry->snd_una, __entry->candidate,
-		__entry->acked_pos
+		__entry->acked_pos, show_svc_tcp_acked_reason(__entry->reason)
 	)
 );
 
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 4eaa8eb4d537..79d8d4e2c90d 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -36,6 +36,7 @@
 #include <net/udp.h>
 #include <net/tcp.h>
 #include <net/tcp_states.h>
+#include <net/tls.h>
 #include <net/tls_prot.h>
 #include <net/handshake.h>
 #include <linux/uaccess.h>
@@ -409,6 +410,14 @@ static void svc_tcp_kill_temp_xprt(struct svc_xprt *xprt)
 	sock_no_linger(svsk->sk_sock->sk);
 }
 
+/* XPT_TLS_SESSION reflects tlshd's report, not an installed TLS ULP. */
+static bool svc_tcp_has_tls_ulp(struct sock *sk)
+{
+	const struct inet_connection_sock *icsk = inet_csk(sk);
+
+	return icsk->icsk_ulp_ops && !strcmp(icsk->icsk_ulp_ops->name, "tls");
+}
+
 /**
  * svc_tcp_handshake_done - Handshake completion handler
  * @data: address of xprt to wake
@@ -426,8 +435,13 @@ static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid)
 	struct svc_sock *svsk = container_of(xprt, struct svc_sock, sk_xprt);
 
 	if (!status) {
+		struct sock *sk = svsk->sk_sk;
+
 		if (peerid != TLS_NO_PEERID)
 			set_bit(XPT_PEER_AUTH, &xprt->xpt_flags);
+		lock_sock(sk);
+		WRITE_ONCE(svsk->sk_tls_ulp, svc_tcp_has_tls_ulp(sk));
+		release_sock(sk);
 		set_bit(XPT_TLS_SESSION, &xprt->xpt_flags);
 	}
 	clear_bit(XPT_HANDSHAKE, &xprt->xpt_flags);
@@ -1401,9 +1415,60 @@ static int svc_tcp_sendmsg(struct svc_sock *svsk, struct svc_rqst *rqstp,
 }
 
 /*
- * Bytes the socket has not seen acknowledged all belong to the most
- * recent replies, so sk_send_pos less that count is the acknowledged
- * position.
+ * A false return means every byte sendmsg() has accepted has reached the
+ * TCP write queue, provided no other blocking sender is on the socket. With
+ * TLS_HW, a sender can drop the socket lock inside tcp_sendmsg_locked()
+ * while its record sits in neither partially_sent_record nor an
+ * open record. TLS_SW keeps the record on tx_list until the push
+ * succeeds. TLS_HW_RECORD keeps its send accounting in the TOE driver,
+ * beyond this check's reach, so TLS_HW_RECORD always counts as pending.
+ *
+ * tx_lock is the documented guard for the partially_sent_* fields, but
+ * every store to them and to tx_list runs with the socket owned: sendmsg(),
+ * the tx worker, and the write-space callback, which the backlog defers
+ * while a process owns the socket. Owning the socket therefore reads a
+ * consistent snapshot without contending with the worker for tx_lock.
+ */
+static bool svc_tls_tx_pending(const struct sock *sk)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	struct tls_sw_context_tx *sw;
+
+	sock_owned_by_me(sk);
+	if (ctx->partially_sent_record || ctx->pending_open_record_frags)
+		return true;
+	switch (ctx->tx_conf) {
+	case TLS_SW:
+		sw = tls_sw_ctx_tx(ctx);
+		return sw->open_rec || !list_empty(&sw->tx_list);
+	case TLS_HW:
+		return false;
+	default:
+		return true;
+	}
+}
+
+/*
+ * In copy mode the strparser pulls a record's segments out of the
+ * receive queue as they arrive, advancing copied_seq, and reports the
+ * record readable only once the last segment is in. copy_mode stays
+ * set until the record is consumed. include/net/tls.h documents the
+ * bitfield as serialized by the socket lock.
+ */
+static bool svc_tls_rx_pending(const struct sock *sk)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+
+	sock_owned_by_me(sk);
+	return tls_sw_ctx_rx(ctx) && tls_sw_ctx_rx(ctx)->strp.copy_mode;
+}
+
+/*
+ * Unacknowledged bytes follow every older reply, and each reply occupies at
+ * least as many wire bytes as sk_send_pos counts for it. sk_send_pos less
+ * the unacknowledged count is therefore at or below the position the client
+ * has acknowledged. Backchannel calls, TLS framing, alerts, the FIN sent at
+ * detach, and a stale snd_una only lower the result.
  *
  * A call the peer sent before it acknowledged a reply precedes the
  * acknowledgment in the stream. When a segment of the call is lost on
@@ -1418,13 +1483,17 @@ static int svc_tcp_sendmsg(struct svc_sock *svsk, struct svc_rqst *rqstp,
  * from moving between the reads and orders them against
  * svc_tcp_acked_pos().
  *
- * Under kTLS, tls_sw_sendmsg() can return the full plaintext count
- * with part of a record still waiting for socket write space, leaving
- * write_seq short of the reply. Whether a record is pending is private
- * to net/tls, so a TLS session publishes no acknowledged position.
+ * kTLS can return a full count from sendmsg() with a record not yet
+ * handed to TCP, and the tls tx worker advances write_seq outside
+ * xpt_mutex. Evaluate svc_tls_tx_pending() and read both sequence numbers
+ * under one lock_sock() so all three describe the same instant. xpt_mutex
+ * keeps every other blocking sender off the socket, as svc_tls_tx_pending()
+ * requires.
+ *
  * tlshd installs the ULP before XPT_TLS_SESSION reports the session,
- * so neither does a reply sent while XPT_HANDSHAKE is set. Test
- * XPT_HANDSHAKE first: it is cleared after XPT_TLS_SESSION is set.
+ * so a reply sent while XPT_HANDSHAKE is set publishes no position. Test
+ * XPT_HANDSHAKE first: svc_tcp_handshake_done() sets XPT_TLS_SESSION before
+ * clearing XPT_HANDSHAKE.
  */
 static void svc_tcp_update_acked(struct svc_sock *svsk)
 {
@@ -1432,15 +1501,21 @@ static void svc_tcp_update_acked(struct svc_sock *svsk)
 	struct tcp_sock *tp = tcp_sk(sk);
 	u64 unacked, acked = 0, acked_pos;
 	u32 write_seq, snd_una, mark;
+	enum svc_tcp_acked_reason reason = SVC_TCP_ACKED_PUBLISHED;
+	bool tls;
 
-	if (test_bit(XPT_HANDSHAKE, &svsk->sk_xprt.xpt_flags) ||
-	    test_bit(XPT_TLS_SESSION, &svsk->sk_xprt.xpt_flags))
+	if (test_bit(XPT_HANDSHAKE, &svsk->sk_xprt.xpt_flags))
 		return;
+	tls = test_bit(XPT_TLS_SESSION, &svsk->sk_xprt.xpt_flags);
+	if (tls && !READ_ONCE(svsk->sk_tls_ulp))
+		reason = SVC_TCP_ACKED_NO_ULP;
 	lock_sock(sk);
+	if (tls && reason == SVC_TCP_ACKED_PUBLISHED && svc_tls_tx_pending(sk))
+		reason = SVC_TCP_ACKED_TX_PENDING;
 	write_seq = READ_ONCE(tp->write_seq);
 	snd_una = READ_ONCE(tp->snd_una);
 	unacked = write_seq - snd_una;
-	if (unacked < svsk->sk_send_pos)
+	if (reason == SVC_TCP_ACKED_PUBLISHED && unacked < svsk->sk_send_pos)
 		acked = svsk->sk_send_pos - unacked;
 	if (svc_xprt_advance_acked(&svsk->sk_xprt, acked)) {
 		if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
@@ -1454,7 +1529,7 @@ static void svc_tcp_update_acked(struct svc_sock *svsk)
 	acked_pos = atomic64_read(&svsk->sk_xprt.xpt_acked_pos);
 	release_sock(sk);
 	trace_svcsock_tcp_acked(&svsk->sk_xprt, svsk->sk_send_pos, write_seq,
-				snd_una, acked, acked_pos);
+				snd_una, acked, acked_pos, reason);
 }
 
 static int svc_tcp_recv_pending(const struct svc_rqst *rqstp)
@@ -1475,6 +1550,13 @@ static int svc_tcp_recv_pending(const struct svc_rqst *rqstp)
  * from its first octet, so a partially read retransmit withholds the
  * position too.
  *
+ * Under kTLS, copied_seq advances by the whole ciphertext record when
+ * the strparser consumes it, before svc_tcp_recv_actor() sees any of
+ * its plaintext. Plaintext left on rx_list and a record ready in the
+ * strparser withhold the position as well; sk_is_readable() reports
+ * both. A record the strparser is still copying in is neither, and
+ * svc_tls_rx_pending() withholds the position for it.
+ *
  * Context: process context. May take and release the socket lock.
  * Return: xpt_acked_pos, or 0 while it cannot be acted on.
  */
@@ -1494,7 +1576,8 @@ static u64 svc_tcp_acked_pos(struct svc_rqst *rqstp)
 
 	lock_sock(sk);
 	pos = atomic64_read(&xprt->xpt_acked_pos);
-	if (svc_tcp_recv_pending(rqstp) ||
+	if (svc_tcp_recv_pending(rqstp) || sk_is_readable(sk) ||
+	    (READ_ONCE(svsk->sk_tls_ulp) && svc_tls_rx_pending(sk)) ||
 	    before(READ_ONCE(tcp_sk(sk)->copied_seq), svsk->sk_acked_rcv))
 		pos = 0;
 	release_sock(sk);

-- 
2.56.0


      parent reply	other threads:[~2026-11-04 14:11 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 14:11 [PATCH v8 00/14] Improve the scalability of NFSD's classic DRC Chuck Lever
2026-11-04 14:11 ` [PATCH v8 01/14] SUNRPC: Reject an AUTH_TLS probe inside a TLS session Chuck Lever
2026-11-04 14:11 ` [PATCH v8 02/14] NFSD: Make the DRC size limit independent of page size Chuck Lever
2026-11-04 14:11 ` [PATCH v8 03/14] NFSD: Remove hard cap on duplicate reply cache size Chuck Lever
2026-11-04 14:11 ` [PATCH v8 04/14] SUNRPC: Assign a unique identifier to each svc_xprt Chuck Lever
2026-11-04 14:11 ` [PATCH v8 05/14] NFSD: Track transport in DRC entries Chuck Lever
2026-11-04 14:11 ` [PATCH v8 06/14] NFSD: Prepare bucket pruning for additional eviction reasons Chuck Lever
2026-11-04 14:11 ` [PATCH v8 07/14] NFSD: Add tracepoints for DRC entry eviction Chuck Lever
2026-11-04 14:11 ` [PATCH v8 08/14] NFSD: Record DRC population in lookup tracepoints Chuck Lever
2026-11-04 14:11 ` [PATCH v8 09/14] SUNRPC: Publish reply positions for upper-layer consumers Chuck Lever
2026-11-04 14:11 ` [PATCH v8 10/14] SUNRPC: Publish TCP reply positions Chuck Lever
2026-11-04 14:11 ` [PATCH v8 11/14] svcrdma: Publish RDMA " Chuck Lever
2026-11-04 14:11 ` [PATCH v8 12/14] NFSD: Evict acknowledged DRC entries Chuck Lever
2026-11-04 14:11 ` [PATCH v8 13/14] NFSD: Remove DRC checksum and payload_misses stat Chuck Lever
2026-11-04 14:11 ` Chuck Lever [this message]

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261104-duplicate-reply-cache-v8-14-eea5b90e4318@kernel.org \
    --to=cel@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=jlayton@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=netdev@vger.kernel.org \
    --cc=okorniev@redhat.com \
    --cc=rmacklem@uoguelph.ca \
    --cc=tom@talpey.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