@@ -291,6 +291,53 @@ func TestWithProxyTransport_HTTPSProxy_HTTP2ToOrigin(t *testing.T) {
291291 }
292292}
293293
294+ func TestWithProxyTransport_HandshakeFailureClosesConn (t * testing.T ) {
295+ // Verify that when the TLS handshake to the origin fails, the underlying
296+ // tunnel connection is closed (regression test for tlsConn.Close on error).
297+ //
298+ // A plain TCP listener acts as the target. The proxy CONNECT succeeds
299+ // (TCP-level), but the subsequent TLS handshake fails because the target
300+ // is not a TLS server. If handshakeTLS properly closes tlsConn on failure,
301+ // the tunnel tears down and the target sees the connection close.
302+ connClosed := make (chan struct {})
303+ ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
304+ if err != nil {
305+ t .Fatalf ("listen: %v" , err )
306+ }
307+ defer ln .Close ()
308+
309+ go func () {
310+ conn , err := ln .Accept ()
311+ if err != nil {
312+ return
313+ }
314+ defer conn .Close ()
315+ // Send non-TLS bytes so the client handshake fails immediately
316+ // rather than waiting for a timeout.
317+ conn .Write ([]byte ("not-tls\n " ))
318+ // Drain until the remote side closes the tunnel.
319+ io .Copy (io .Discard , conn )
320+ close (connClosed )
321+ }()
322+
323+ proxyURL , _ := startProxy (t , true )
324+ transport := withProxyTransport (newTestTransport (), proxyURL , "" )
325+ t .Cleanup (transport .CloseIdleConnections )
326+ client := & http.Client {Transport : transport , Timeout : 5 * time .Second }
327+
328+ _ , err = client .Get ("https://" + ln .Addr ().String ())
329+ if err == nil {
330+ t .Fatal ("expected TLS handshake error, got nil" )
331+ }
332+
333+ select {
334+ case <- connClosed :
335+ // Connection was properly cleaned up.
336+ case <- time .After (5 * time .Second ):
337+ t .Fatal ("connection was not closed after TLS handshake failure" )
338+ }
339+ }
340+
294341func TestWithProxyTransport_ProxyRejectsConnect (t * testing.T ) {
295342 tests := []struct {
296343 name string
0 commit comments