From 5547d4a2f41ec058e6f9bddfe2a7be012a0f24a9 Mon Sep 17 00:00:00 2001 From: drmabus Date: Thu, 19 Mar 2026 14:11:52 +0700 Subject: [PATCH 1/2] fix: make ztls handshake timeout interruptible Handshake() was executed inline inside a select, making the timeout ineffective. This change moves the handshake into a goroutine and properly races ctx.Done() against the result. --- pkg/tlsx/ztls/ztls.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/tlsx/ztls/ztls.go b/pkg/tlsx/ztls/ztls.go index a03b7267..81b0092e 100644 --- a/pkg/tlsx/ztls/ztls.go +++ b/pkg/tlsx/ztls/ztls.go @@ -323,17 +323,18 @@ func (c *Client) getConfig(hostname, ip, port string, options clients.ConnectOpt // tlsHandshakeWithCtx attempts tls handshake with given timeout func (c *Client) tlsHandshakeWithTimeout(tlsConn *tls.Conn, ctx context.Context) error { errChan := make(chan error, 1) - defer close(errChan) + + go func() { + errChan <- tlsConn.Handshake() + }() select { case <-ctx.Done(): return errorutil.NewWithTag("ztls", "timeout while attempting handshake") //nolint - case errChan <- tlsConn.Handshake(): - } - - err := <-errChan - if err == tls.ErrCertsOnly { - err = nil + case err := <-errChan: + if err == tls.ErrCertsOnly { + err = nil + } + return err } - return err } From 38ca9db45845b72a951048b64aabef53c2e156c4 Mon Sep 17 00:00:00 2001 From: drmabus Date: Thu, 19 Mar 2026 14:26:43 +0700 Subject: [PATCH 2/2] chore: use errors.Is for ErrCertsOnly comparison --- pkg/tlsx/ztls/ztls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tlsx/ztls/ztls.go b/pkg/tlsx/ztls/ztls.go index 81b0092e..30af0d3f 100644 --- a/pkg/tlsx/ztls/ztls.go +++ b/pkg/tlsx/ztls/ztls.go @@ -332,7 +332,7 @@ func (c *Client) tlsHandshakeWithTimeout(tlsConn *tls.Conn, ctx context.Context) case <-ctx.Done(): return errorutil.NewWithTag("ztls", "timeout while attempting handshake") //nolint case err := <-errChan: - if err == tls.ErrCertsOnly { + if errors.Is(err, tls.ErrCertsOnly) { err = nil } return err