TLS read_sock performance scalability
Subsystems (4)
Chuck Lever
maintainerv3 cover TLS read_sock performance scalability
From: Chuck Lever <[email protected]>
I'd like to encourage in-kernel kTLS consumers (i.e., NFS and NVMe/TCP) to coalesce on the use of read_sock. When I suggested this to Hannes, he reported a number of nagging performance scalability issues with read_sock. This series is an attempt to run these issues down and get them fixed before we convert the above sock_recvmsg consumers over to read_sock.
While I assemble performance data, let's nail down the preferred code structure.
Base commit: 05e059510edf ("Merge branch 'eth-fbnic-add-fbnic-self-tests'")
---
Changes since v2:
- Fix short read self tests
Changes since v1:
- Add C11 reference
- Extend data_ready reduction to recvmsg and splice
- Restructure read_sock and recvmsg using shared helpers
Chuck Lever (8):
tls: Factor tls_decrypt_async_drain() from recvmsg
tls: Factor tls_rx_decrypt_record() helper
tls: Fix dangling skb pointer in tls_sw_read_sock()
tls: Factor tls_strp_msg_release() from tls_strp_msg_done()
tls: Suppress spurious saved_data_ready on all receive paths
tls: Flush backlog before tls_rx_rec_wait in read_sock
tls: Restructure tls_sw_read_sock() into submit/deliver phases
tls: Enable batch async decryption in read_sock
net/tls/tls.h | 3 +- net/tls/tls_strp.c | 34 ++++++-- net/tls/tls_sw.c | 213 ++++++++++++++++++++++++++++++++------------- 3 files changed, 184 insertions(+), 66 deletions(-)
Signature · 1 line
2.52.0
Chuck Lever
maintainerPATCH v3 1/8 tls: Factor tls_decrypt_async_drain() from recvmsg
From: Chuck Lever <[email protected]>
The recvmsg path pairs tls_decrypt_async_wait() with __skb_queue_purge(&ctx->async_hold). Bundling the two into tls_decrypt_async_drain() gives later patches a single call for async teardown.
--- net/tls/tls_sw.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)
net/tls/tls_sw.c +13 -2
@@ -249,6 +249,18 @@ static int tls_decrypt_async_wait(struct tls_sw_context_rx *ctx)return ctx->async_wait.err; } +/* Collect all pending async AEAD completions and release the +* skbs held for them. Returns the crypto error if any +* operation failed, zero otherwise. +*/ +static int tls_decrypt_async_drain (struct tls_sw_context_rx *ctx) +{ +int ret =tls_decrypt_async_wait (ctx); + +__skb_queue_purge (&ctx->async_hold); +return ret; +} + static int tls_do_decryption(struct sock *sk, struct scatterlist *sgin, struct scatterlist *sgout,
@@ -2223,8 +2235,7 @@ int tls_sw_recvmsg(struct sock *sk,int ret;/* Wait for all previously submitted records to be decrypted */ - ret =tls_decrypt_async_wait (ctx); -__skb_queue_purge (&ctx->async_hold); + ret =tls_decrypt_async_drain (ctx);if (ret) {if (err >=0 || err == -EINPROGRESS )
Signature · 1 line
2.52.0
Re: [PATCH v3 1/8] tls: Factor tls_decrypt_async_drain() from recvmsg
Quoted 10 lines · Chuck Lever
From: Chuck Lever <[email protected]>
The recvmsg path pairs
tls_decrypt_async_wait()with__skb_queue_purge(&ctx->async_hold). Bundling the two intotls_decrypt_async_drain()gives later patches a single call for async teardown.Reviewed-by Hannes Reinecke <[email protected]>Signed-off-by Chuck Lever <[email protected]>
Alistair
Quoted 41 lines
--- net/tls/tls_sw.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-)net/tls/tls_sw.c +13
-3@@ -249,6 +249,18 @@ static int tls_decrypt_async_wait(struct tls_sw_context_rx *ctx)return ctx->async_wait.err; } +/* Collect all pending async AEAD completions and release the +* skbs held for them. Returns the crypto error if any +* operation failed, zero otherwise. +*/ +static int tls_decrypt_async_drain (struct tls_sw_context_rx *ctx) +{ +int ret =tls_decrypt_async_wait (ctx); + +__skb_queue_purge (&ctx->async_hold); +return ret; +} + static int tls_do_decryption(struct sock *sk, struct scatterlist *sgin, struct scatterlist *sgout,@@ -2223,8 +2235,7 @@ int tls_sw_recvmsg(struct sock *sk,int ret;/* Wait for all previously submitted records to be decrypted */ - ret =tls_decrypt_async_wait (ctx); -__skb_queue_purge (&ctx->async_hold); + ret =tls_decrypt_async_drain (ctx);if (ret) {if (err >=0 || err == -EINPROGRESS ) --2.52.0
Sabrina Dubroca
maintainerRe: [PATCH v3 1/8] tls: Factor tls_decrypt_async_drain() from recvmsg
In reply to Chuck LeverQuoted 6 lines · Chuck Lever
From: Chuck Lever <[email protected]>
The recvmsg path pairs
tls_decrypt_async_wait()with__skb_queue_purge(&ctx->async_hold). Bundling the two intotls_decrypt_async_drain()gives later patches a single call for async teardown.
I was wondering if tls_decrypt_async_wait() without __skb_queue_purge() is ever the right thing. Once we've waited for all pending decryptions, async_hold's job is done, we don't need to keep all that memory around anymore.
Should we just move recvmsg's __skb_queue_purge() into tls_decrypt_async_wait()?
Signature · 1 line
Sabrina
Chuck Lever
maintainerPATCH v3 2/8 tls: Factor tls_rx_decrypt_record() helper
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
recvmsg, read_sock, and splice_read each open-code the same sequence: zero-initialize the decrypt arguments, call tls_rx_one_record(), and abort the connection on failure.
Extract tls_rx_decrypt_record() so each receive path shares a single decrypt-and-abort primitive. Each call site still initializes darg.inargs separately, since recvmsg sets zc and async between the memset and the decrypt call.
--- net/tls/tls_sw.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
net/tls/tls_sw.c +17 -12
@@ -1832,6 +1832,17 @@ static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,return tls_check_pending_rekey (sk, tls_ctx, darg->skb); } +/* Decrypt one record and abort the connection on failure. */ +static int tls_rx_decrypt_record (struct sock *sk,struct msghdr *msg, +struct tls_decrypt_arg *darg) +{ +int err =tls_rx_one_record (sk, msg, darg); + +if (err <0 ) +tls_err_abort (sk, -EBADMSG ); +return err; +} +int decrypt_skb (struct sock *sk,struct scatterlist *sgout) {struct tls_decrypt_arg darg = { .zc = true, };
@@ -2132,11 +2143,9 @@ int tls_sw_recvmsg(struct sock *sk,else darg.async = false; - err =tls_rx_one_record (sk, msg, &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk, msg, &darg); +if (err <0 ) goto recv_end; - } async |= darg.async;
@@ -2294,11 +2303,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,memset (&darg.inargs,0 ,sizeof (darg.inargs)); - err =tls_rx_one_record (sk,NULL , &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk,NULL , &darg); +if (err <0 ) goto splice_read_end; - }tls_rx_rec_done (ctx); skb = darg.skb;
@@ -2380,11 +2387,9 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,memset (&darg.inargs,0 ,sizeof (darg.inargs)); - err =tls_rx_one_record (sk,NULL , &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk,NULL , &darg); +if (err <0 ) goto read_sock_end; - } released = tls_read_flush_backlog(sk, prot, INT_MAX, 0, decrypted,
Signature · 1 line
2.52.0
Re: [PATCH v3 2/8] tls: Factor tls_rx_decrypt_record() helper
Quoted 7 lines · Chuck Lever
From: Chuck Lever <[email protected]>
recvmsg,
read_sock, andsplice_readeach open-code the same sequence: zero-initialize the decrypt arguments, calltls_rx_one_record(), and abort the connection on failure.
Quoted 7 lines
Extract
tls_rx_decrypt_record()so each receive path shares a single decrypt-and-abort primitive. Each call site still initializes darg.inargs separately, since recvmsg sets zc and async between the memset and the decrypt call.Reviewed-by Hannes Reinecke <[email protected]>Signed-off-by Chuck Lever <[email protected]>
Alistair
Quoted 72 lines
--- net/tls/tls_sw.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)net/tls/tls_sw.c +17
-13@@ -1832,6 +1832,17 @@ static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,return tls_check_pending_rekey (sk, tls_ctx, darg->skb); } +/* Decrypt one record and abort the connection on failure. */ +static int tls_rx_decrypt_record (struct sock *sk,struct msghdr *msg, +struct tls_decrypt_arg *darg) +{ +int err =tls_rx_one_record (sk, msg, darg); + +if (err <0 ) +tls_err_abort (sk, -EBADMSG ); +return err; +} +int decrypt_skb (struct sock *sk,struct scatterlist *sgout) {struct tls_decrypt_arg darg = { .zc = true, };@@ -2132,11 +2143,9 @@ int tls_sw_recvmsg(struct sock *sk,else darg.async = false; - err =tls_rx_one_record (sk, msg, &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk, msg, &darg); +if (err <0 ) goto recv_end; - } async |= darg.async;@@ -2294,11 +2303,9 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,memset (&darg.inargs,0 ,sizeof (darg.inargs)); - err =tls_rx_one_record (sk,NULL , &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk,NULL , &darg); +if (err <0 ) goto splice_read_end; - }tls_rx_rec_done (ctx); skb = darg.skb;@@ -2380,11 +2387,9 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,memset (&darg.inargs,0 ,sizeof (darg.inargs)); - err =tls_rx_one_record (sk,NULL , &darg); -if (err <0 ) { -tls_err_abort (sk, -EBADMSG ); + err =tls_rx_decrypt_record (sk,NULL , &darg); +if (err <0 ) goto read_sock_end; - } released = tls_read_flush_backlog(sk, prot, INT_MAX, 0, decrypted, --2.52.0
Sabrina Dubroca
maintainerRe: [PATCH v3 2/8] tls: Factor tls_rx_decrypt_record() helper
In reply to Chuck LeverQuoted 10 lines · Chuck Lever
From: Chuck Lever <[email protected]>
recvmsg,
read_sock, andsplice_readeach open-code the same sequence: zero-initialize the decrypt arguments, calltls_rx_one_record(), and abort the connection on failure.Extract
tls_rx_decrypt_record()so each receive path shares a single decrypt-and-abort primitive. Each call site still initializes darg.inargs separately, since recvmsg sets zc and async between the memset and the decrypt call.
Is there any reason to keep tls_rx_one_record()? You're replacing all existing callers, and not introducing new users in this series. Seems like what you want is just move the tls_err_abort() into tls_rx_one_record().
(I'm not convinced that "abort the connection on every error (decrypt fail or ENOMEM or whatever)" is right, but that's a separate question)
Signature · 1 line
Sabrina
Re: [PATCH v3 2/8] tls: Factor tls_rx_decrypt_record() helper
Quoted 12 lines · Sabrina Dubroca
Quoted 10 lines · Chuck Lever
From: Chuck Lever <[email protected]>
recvmsg,
read_sock, andsplice_readeach open-code the same sequence: zero-initialize the decrypt arguments, calltls_rx_one_record(), and abort the connection on failure.Extract
tls_rx_decrypt_record()so each receive path shares a single decrypt-and-abort primitive. Each call site still initializes darg.inargs separately, since recvmsg sets zc and async between the memset and the decrypt call.
Quoted 8 lines
Is there any reason to keep
tls_rx_one_record()? You're replacing all existing callers, and not introducing new users in this series. Seems like what you want is just move thetls_err_abort()intotls_rx_one_record().(I'm not convinced that "abort the connection on every error (decrypt fail or ENOMEM or whatever)" is right, but that's a separate question)
It certainly is the sane option. Any error should be considered a transmission error, so something has happened during transmission. As the main point of TLS is transmission security, we cannot assume anything (like being able to pick up the existing connection again), and have to drop the connection to re-establish the security context.
Cheers,
Hannes
Signature · 4 lines
Dr. Hannes Reinecke Kernel Storage Architect [email protected] +49 911 74053 688 SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
Chuck Lever
maintainerPATCH v3 3/8 tls: Fix dangling skb pointer in tls_sw_read_sock()
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
Per ISO/IEC 9899:2011 section 6.2.4p2, a pointer value becomes indeterminate when the object it points to reaches the end of its lifetime; Annex J.2 classifies the use of such a value as undefined behavior. In tls_sw_read_sock(), consume_skb(skb) in the fully-consumed path frees the skb, but the "do { } while (skb)" loop condition then evaluates that freed pointer. Although the value is never dereferenced -- the loop either continues and overwrites skb, or exits -- any future change that adds a dereference between consume_skb() and the loop condition would produce a silent use-after-free.
--- net/tls/tls_sw.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
net/tls/tls_sw.c +4 -3
@@ -2373,7 +2373,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, goto read_sock_end; decrypted =0 ; - do { +for (;;) {if (!skb_queue_empty (&ctx->rx_list)) { skb =__skb_dequeue (&ctx->rx_list); rxm =strp_msg (skb);
@@ -2422,10 +2422,11 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, goto read_sock_requeue; }else {consume_skb (skb); + skb =NULL ;if (!desc->count) - skb =NULL ; +break ; } - }while (skb); + } read_sock_end:tls_rx_reader_release (sk, ctx);
Signature · 1 line
2.52.0
Chuck Lever
maintainerPATCH v3 4/8 tls: Factor tls_strp_msg_release() from tls_strp_msg_done()
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
tls_strp_msg_done() conflates releasing the current record with checking for the next one via tls_strp_check_rcv(). Batch processing requires releasing a record without immediately triggering that check, so the release step is separated into tls_strp_msg_release(). tls_strp_msg_done() is preserved as a wrapper for existing callers.
--- net/tls/tls.h | 1 + net/tls/tls_strp.c | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-)
net/tls/tls.h +1 -0
@@ -193,6 +193,7 @@ int tls_strp_init(struct tls_strparser *strp, struct sock *sk);void tls_strp_data_ready (struct tls_strparser *strp);void tls_strp_check_rcv (struct tls_strparser *strp); +void tls_strp_msg_release (struct tls_strparser *strp);void tls_strp_msg_done (struct tls_strparser *strp);int tls_rx_msg_size (struct tls_strparser *strp,struct sk_buff *skb);
net/tls/tls_strp.c +14 -1
@@ -581,7 +581,16 @@ static void tls_strp_work(struct work_struct *w)release_sock (strp->sk); } -void tls_strp_msg_done (struct tls_strparser *strp) +/** +* tls_strp_msg_release - release the current strparser message +* @strp: TLS stream parser instance +* +* Release the current record without triggering a check for the +* next record. Callers must invoke tls_strp_check_rcv() before +* releasing the socket lock, or queued data will stall until +* the next tls_strp_data_ready() event. +*/ +void tls_strp_msg_release (struct tls_strparser *strp) {WARN_ON (!strp->stm.full_len);
@@ -592,7 +601,11 @@ void tls_strp_msg_done(struct tls_strparser *strp)WRITE_ONCE (strp->msg_ready,0 );memset (&strp->stm,0 ,sizeof (strp->stm)); +} +void tls_strp_msg_done (struct tls_strparser *strp) +{ +tls_strp_msg_release (strp);tls_strp_check_rcv (strp); }
Signature · 1 line
2.52.0
Chuck Lever
maintainerPATCH v3 5/8 tls: Suppress spurious saved_data_ready on all receive paths
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
Each record release via tls_strp_msg_done() triggers tls_strp_check_rcv(), which calls tls_rx_msg_ready() and fires saved_data_ready(). During a multi-record receive, the first N-1 wakeups are pure overhead: the caller is already running and will pick up subsequent records on the next loop iteration. The same waste occurs on the recvmsg and splice_read paths.
Replace tls_strp_msg_done() with tls_strp_msg_release() in all three receive paths (read_sock, recvmsg, splice_read), deferring the tls_strp_check_rcv() call to each path's exit point. Factor tls_rx_msg_ready() out of tls_strp_read_sock() so that parsing a record no longer fires the callback directly, and introduce tls_strp_check_rcv_quiet() for use in tls_rx_rec_wait(), which parses queued data without notifying.
With no remaining callers, tls_strp_msg_done() and its wrapper tls_rx_rec_done() are removed.
--- net/tls/tls.h | 2 +- net/tls/tls_strp.c | 27 +++++++++++++++++++-------- net/tls/tls_sw.c | 21 ++++++++++++++------- 3 files changed, 34 insertions(+), 16 deletions(-)
net/tls/tls.h +1 -1
@@ -193,8 +193,8 @@ int tls_strp_init(struct tls_strparser *strp, struct sock *sk);void tls_strp_data_ready (struct tls_strparser *strp);void tls_strp_check_rcv (struct tls_strparser *strp); +void tls_strp_check_rcv_quiet (struct tls_strparser *strp);void tls_strp_msg_release (struct tls_strparser *strp); -void tls_strp_msg_done (struct tls_strparser *strp);int tls_rx_msg_size (struct tls_strparser *strp,struct sk_buff *skb);void tls_rx_msg_ready (struct tls_strparser *strp);
net/tls/tls_strp.c +19 -8
@@ -368,7 +368,6 @@ static int tls_strp_copyin(read_descriptor_t *desc, struct sk_buff *in_skb, desc->count =0 ;WRITE_ONCE (strp->msg_ready,1 ); -tls_rx_msg_ready (strp); }return ret;
@@ -539,11 +538,27 @@ static int tls_strp_read_sock(struct tls_strparser *strp)return tls_strp_read_copy (strp, false);WRITE_ONCE (strp->msg_ready,1 ); -tls_rx_msg_ready (strp);return 0 ; } +/** +* tls_strp_check_rcv_quiet - parse without consumer notification +* @strp: TLS stream parser instance +* +* Parse queued data without firing the consumer notification. A subsequent +* tls_strp_check_rcv() is required before the socket lock is released; +* otherwise queued data stalls until the next tls_strp_data_ready() event. +*/ +void tls_strp_check_rcv_quiet (struct tls_strparser *strp) +{ +if (unlikely (strp->stopped) || strp->msg_ready) +return ; + +if (tls_strp_read_sock (strp) == -ENOMEM ) +queue_work (tls_strp_wq, &strp->work); +} +void tls_strp_check_rcv (struct tls_strparser *strp) {if (unlikely (strp->stopped) || strp->msg_ready)
@@ -551,6 +566,8 @@ void tls_strp_check_rcv(struct tls_strparser *strp)if (tls_strp_read_sock (strp) == -ENOMEM )queue_work (tls_strp_wq, &strp->work); +else if (strp->msg_ready) +tls_rx_msg_ready (strp); }/* Lower sock lock held */
@@ -603,12 +620,6 @@ void tls_strp_msg_release(struct tls_strparser *strp)memset (&strp->stm,0 ,sizeof (strp->stm)); } -void tls_strp_msg_done (struct tls_strparser *strp) -{ -tls_strp_msg_release (strp); -tls_strp_check_rcv (strp); -} -void tls_strp_stop (struct tls_strparser *strp) { strp->stopped =1 ;
net/tls/tls_sw.c +14 -7
@@ -1384,7 +1384,10 @@ tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock,return ret;if (!skb_queue_empty (&sk->sk_receive_queue)) { -tls_strp_check_rcv (&ctx->strp); +/* tls_strp_check_rcv() is called at each receive +* path's exit before the socket lock is released. +*/ +tls_strp_check_rcv_quiet (&ctx->strp);if (tls_strp_msg_ready (ctx))break ; }
@@ -1876,9 +1879,9 @@ static int tls_record_content_type(struct msghdr *msg, struct tls_msg *tlm,return 1 ; } -static void tls_rx_rec_done (struct tls_sw_context_rx *ctx) +static void tls_rx_rec_release (struct tls_sw_context_rx *ctx) { -tls_strp_msg_done (&ctx->strp); +tls_strp_msg_release (&ctx->strp); }/* This function traverses the rx_list in tls receive context to copies the
@@ -2159,7 +2162,7 @@ int tls_sw_recvmsg(struct sock *sk, err =tls_record_content_type (msg,tls_msg (darg.skb), &control);if (err <=0 ) {DEBUG_NET_WARN_ON_ONCE (darg.zc); -tls_rx_rec_done (ctx); +tls_rx_rec_release (ctx); put_on_rx_list_err:__skb_queue_tail (&ctx->rx_list, darg.skb); goto recv_end;
@@ -2173,7 +2176,8 @@ int tls_sw_recvmsg(struct sock *sk,/* TLS 1.3 may have updated the length by more than overhead */ rxm =strp_msg (darg.skb); chunk = rxm->full_len; -tls_rx_rec_done (ctx); +tls_rx_rec_release (ctx); +tls_strp_check_rcv_quiet (&ctx->strp);if (!darg.zc) {bool partially_consumed = chunk > len;
@@ -2267,6 +2271,7 @@ int tls_sw_recvmsg(struct sock *sk, copied += decrypted; end: +tls_strp_check_rcv (&ctx->strp);tls_rx_reader_unlock (sk, ctx);if (psock)sk_psock_put (sk, psock);
@@ -2307,7 +2312,7 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,if (err <0 ) goto splice_read_end; -tls_rx_rec_done (ctx); +tls_rx_rec_release (ctx); skb = darg.skb; }
@@ -2334,6 +2339,7 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,consume_skb (skb); splice_read_end: +tls_strp_check_rcv (&ctx->strp);tls_rx_reader_unlock (sk, ctx);return copied ? : err;
@@ -2399,7 +2405,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, tlm =tls_msg (skb); decrypted += rxm->full_len; -tls_rx_rec_done (ctx); +tls_rx_rec_release (ctx); }/* read_sock does not support reading control messages */
@@ -2429,6 +2435,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, } read_sock_end: +tls_strp_check_rcv (&ctx->strp);tls_rx_reader_release (sk, ctx);return copied ? : err;
Signature · 1 line
2.52.0
Chuck Lever
maintainerPATCH v3 6/8 tls: Flush backlog before tls_rx_rec_wait in read_sock
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
While lock_sock is held during read_sock, incoming TCP segments land on sk->sk_backlog rather than sk->sk_receive_queue. tls_rx_rec_wait() inspects only sk_receive_queue, so backlog data remains invisible until release_sock() drains it, forcing an extra workqueue cycle for records that arrive during decryption.
Calling sk_flush_backlog() before tls_rx_rec_wait() moves backlog data into sk_receive_queue, where tls_strp_check_rcv() can parse it immediately. The existing tls_read_flush_backlog call after decryption is retained for TCP window management.
--- net/tls/tls_sw.c | 5 +++++ 1 file changed, 5 insertions(+)
net/tls/tls_sw.c +5 -0
@@ -2387,6 +2387,11 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, }else {struct tls_decrypt_arg darg; +/* Drain backlog so segments that arrived while the +* lock was held appear on sk_receive_queue before +* tls_rx_rec_wait waits for a new record. +*/ +sk_flush_backlog (sk); err =tls_rx_rec_wait (sk,NULL , true, released);if (err <=0 ) goto read_sock_end;
Signature · 1 line
2.52.0
Sabrina Dubroca
maintainerRe: [PATCH v3 6/8] tls: Flush backlog before tls_rx_rec_wait in read_sock
Quoted 9 lines · Chuck Lever
From: Chuck Lever <[email protected]>
While
lock_sockis held duringread_sock, incoming TCP segments land onsk->sk_backlograther thansk->sk_receive_queue.tls_rx_rec_wait()inspects onlysk_receive_queue, so backlog data remains invisible untilrelease_sock()drains it, forcing an extra workqueue cycle for records that arrive during decryption.
Quoted 4 lines
Calling
sk_flush_backlog()beforetls_rx_rec_wait()moves backlog data intosk_receive_queue, wheretls_strp_check_rcv()can parse it immediately. The existingtls_read_flush_backlogcall after decryption is retained for TCP window management.
I'm really confused by this.
- Why is the existing tls_read_flush_backlog not enough?
- and what is it still accomplishing now that we're flushing before every tls_rx_rec_wait?
- Why are other RX paths not affected?
You first paragraph and the comment in the diff kind of say the problem is that tls_rx_rec_wait() doesn't try to feed from the backlog when sk_receive_queue is empty.
Quoted 12 lines
net/tls/tls_sw.c +5
-0@@ -2387,6 +2387,11 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, }else {struct tls_decrypt_arg darg; +/* Drain backlog so segments that arrived while the +* lock was held appear on sk_receive_queue before +* tls_rx_rec_wait waits for a new record. +*/ +sk_flush_backlog (sk); err =tls_rx_rec_wait (sk,NULL , true, released);if (err <=0 ) goto read_sock_end;
Signature · 1 line
Sabrina
Chuck Lever
maintainerPATCH v3 7/8 tls: Restructure tls_sw_read_sock() into submit/deliver phases
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
Pipelining multiple AEAD operations requires separating decryption from delivery so that several records can be submitted before any are passed to the read_actor callback. The main loop in tls_sw_read_sock() is split into two explicit phases: a submit phase that decrypts one record onto ctx->rx_list, and a deliver phase that drains rx_list and passes each cleartext skb to the read_actor callback.
With a single record per submit phase, behavior is identical to the previous code. A subsequent patch will extend the submit phase to pipeline multiple AEAD operations.
--- net/tls/tls_sw.c | 79 +++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 38 deletions(-)
net/tls/tls_sw.c +41 -38
@@ -2354,8 +2354,8 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,struct tls_context *tls_ctx =tls_get_ctx (sk);struct tls_sw_context_rx *ctx =tls_sw_ctx_rx (tls_ctx);struct tls_prot_info *prot = &tls_ctx->prot_info; -struct strp_msg *rxm =NULL ;struct sk_buff *skb =NULL ; +struct strp_msg *rxm;struct sk_psock *psock;size_t flushed_at =0 ;bool released = true;
@@ -2380,17 +2380,15 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, decrypted =0 ;for (;;) { -if (!skb_queue_empty (&ctx->rx_list)) { - skb =__skb_dequeue (&ctx->rx_list); - rxm =strp_msg (skb); - tlm =tls_msg (skb); - }else { -struct tls_decrypt_arg darg; +struct tls_decrypt_arg darg; -/* Drain backlog so segments that arrived while the -* lock was held appear on sk_receive_queue before -* tls_rx_rec_wait waits for a new record. -*/ +/* Phase 1: Submit -- decrypt one record onto rx_list. +* Flush the backlog first so that segments that +* arrived while the lock was held appear on +* sk_receive_queue before tls_rx_rec_wait waits +* for a new record. +*/ +if (skb_queue_empty (&ctx->rx_list)) {sk_flush_backlog (sk); err =tls_rx_rec_wait (sk,NULL , true, released);if (err <=0 )
@@ -2405,38 +2403,43 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, released =tls_read_flush_backlog (sk, prot,INT_MAX ,0 , decrypted, &flushed_at); - skb = darg.skb; + decrypted +=strp_msg (darg.skb)->full_len; +tls_rx_rec_release (ctx); +__skb_queue_tail (&ctx->rx_list, darg.skb); + } + +/* Phase 2: Deliver -- drain rx_list to read_actor */ +while ((skb =__skb_dequeue (&ctx->rx_list)) !=NULL ) { rxm =strp_msg (skb); tlm =tls_msg (skb); - decrypted += rxm->full_len; -tls_rx_rec_release (ctx); - } - -/* read_sock does not support reading control messages */ -if (tlm->control !=TLS_RECORD_TYPE_DATA ) { - err = -EINVAL ; - goto read_sock_requeue; - } - - used =read_actor (desc, skb, rxm->offset, rxm->full_len); -if (used <=0 ) { -if (!copied) - err = used; - goto read_sock_requeue; - } - copied += used; -if (used < rxm->full_len) { - rxm->offset += used; - rxm->full_len -= used; -if (!desc->count) +/* read_sock does not support reading control messages */ +if (tlm->control !=TLS_RECORD_TYPE_DATA ) { + err = -EINVAL ; goto read_sock_requeue; - }else { -consume_skb (skb); - skb =NULL ; -if (!desc->count) -break ; + } + + used =read_actor (desc, skb, rxm->offset, + rxm->full_len); +if (used <=0 ) { +if (!copied) + err = used; + goto read_sock_requeue; + } + copied += used; +if (used < rxm->full_len) { + rxm->offset += used; + rxm->full_len -= used; +if (!desc->count) + goto read_sock_requeue; + }else { +consume_skb (skb); + skb =NULL ; + } } +/* Drain all of rx_list before honoring !desc->count */ +if (!desc->count) +break ; } read_sock_end:
Signature · 1 line
2.52.0
Chuck Lever
maintainerPATCH v3 8/8 tls: Enable batch async decryption in read_sock
In reply to Chuck LeverFrom: Chuck Lever <[email protected]>
tls_sw_read_sock() decrypts one TLS record at a time, blocking until each AEAD operation completes before proceeding. Hardware async crypto engines depend on pipelining multiple operations to achieve full throughput, and the one-at-a-time model prevents that. Kernel consumers such as NVMe-TCP and NFSD (when using TLS) are therefore unable to benefit from hardware offload.
When ctx->async_capable is true, the submit phase now loops up to TLS_READ_SOCK_BATCH (16) records. The first record waits via tls_rx_rec_wait(); subsequent iterations use tls_strp_msg_ready() and tls_strp_check_rcv() to collect records already queued on the socket without blocking. Each record is submitted with darg.async set, and all resulting skbs are appended to rx_list.
After the submit loop, a single tls_decrypt_async_drain() collects all pending AEAD completions before the deliver phase passes cleartext records to the consumer. The batch bound of 16 limits concurrent memory consumption to 16 cleartext skbs plus their AEAD contexts. If async_capable is false, the loop exits after one record and the async wait is skipped, preserving prior behavior.
--- net/tls/tls_sw.c | 95 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 17 deletions(-)
net/tls/tls_sw.c +78 -17
@@ -261,6 +261,12 @@ static int tls_decrypt_async_drain(struct tls_sw_context_rx *ctx)return ret; } +/* Submit an AEAD decrypt request. On success with darg->async set, +* the caller must not touch aead_req; the completion handler frees +* it. Every error return clears darg->async and guarantees no +* in-flight AEAD operation remains -- callers rely on this to +* safely free aead_req and to skip async drain on error paths. +*/ static int tls_do_decryption(struct sock *sk, struct scatterlist *sgin, struct scatterlist *sgout,
@@ -2348,6 +2354,13 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, goto splice_read_end; } +/* Bound on concurrent async AEAD submissions per read_sock +* call. Chosen to fill typical hardware crypto pipelines +* without excessive memory consumption (each in-flight record +* holds one cleartext skb plus its AEAD request context). +*/ +#define TLS_READ_SOCK_BATCH 16 +int tls_sw_read_sock (struct sock *sk,read_descriptor_t *desc,sk_read_actor_t read_actor) {
@@ -2359,6 +2372,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,struct sk_psock *psock;size_t flushed_at =0 ;bool released = true; +bool async = false;struct tls_msg *tlm;ssize_t copied =0 ;ssize_t decrypted;
@@ -2381,31 +2395,68 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, decrypted =0 ;for (;;) {struct tls_decrypt_arg darg; +int nr_async =0 ; -/* Phase 1: Submit -- decrypt one record onto rx_list. +/* Phase 1: Submit -- decrypt records onto rx_list. * Flush the backlog first so that segments that * arrived while the lock was held appear on * sk_receive_queue before tls_rx_rec_wait waits * for a new record. */ if (skb_queue_empty (&ctx->rx_list)) { -sk_flush_backlog (sk); - err =tls_rx_rec_wait (sk,NULL , true, released); -if (err <=0 ) +while (nr_async <TLS_READ_SOCK_BATCH ) { +if (nr_async ==0 ) { +sk_flush_backlog (sk); + err =tls_rx_rec_wait (sk,NULL , + true, + released); +if (err <=0 ) + goto read_sock_end; + }else { +if (!tls_strp_msg_ready (ctx)) { +tls_strp_check_rcv_quiet (&ctx->strp); +if (!tls_strp_msg_ready (ctx)) +break ; + } +if (!tls_strp_msg_load (&ctx->strp, + released)) +break ; + } + +memset (&darg.inargs,0 ,sizeof (darg.inargs)); + darg.async = ctx->async_capable; + + err =tls_rx_decrypt_record (sk,NULL , + &darg); +if (err <0 ) + goto read_sock_end; + + async |= darg.async; + released =tls_read_flush_backlog (sk, prot, +INT_MAX , +0 , + decrypted, + &flushed_at); + decrypted +=strp_msg (darg.skb)->full_len; +tls_rx_rec_release (ctx); +__skb_queue_tail (&ctx->rx_list, darg.skb); + nr_async++; + +if (!ctx->async_capable) +break ; + } + } + +/* Async wait -- collect pending AEAD completions */ +if (async) { +int ret =tls_decrypt_async_drain (ctx); + + async = false; +if (ret) { +__skb_queue_purge (&ctx->rx_list); + err = ret; goto read_sock_end; - -memset (&darg.inargs,0 ,sizeof (darg.inargs)); - - err =tls_rx_decrypt_record (sk,NULL , &darg); -if (err <0 ) - goto read_sock_end; - - released =tls_read_flush_backlog (sk, prot,INT_MAX , -0 , decrypted, - &flushed_at); - decrypted +=strp_msg (darg.skb)->full_len; -tls_rx_rec_release (ctx); -__skb_queue_tail (&ctx->rx_list, darg.skb); + } }/* Phase 2: Deliver -- drain rx_list to read_actor */
@@ -2443,6 +2494,16 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, } read_sock_end: +if (async) { +int ret =tls_decrypt_async_drain (ctx); + +__skb_queue_purge (&ctx->rx_list); +/* Preserve the error that triggered early exit; +* a crypto drain error is secondary. +*/ +if (ret && !err) + err = ret; + }tls_strp_check_rcv (&ctx->strp);tls_rx_reader_release (sk, ctx);return copied ? : err;
Signature · 1 line
2.52.0