-
Notifications
You must be signed in to change notification settings - Fork 151
fix: resolve indefinite hang in ztls handshake #894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| package ztls | ||
|
|
||
| import ( | ||
| "context" | ||
| "net" | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/zmap/zcrypto/tls" | ||
| ) | ||
|
|
||
| // hangingConn is a net.Conn that blocks on Read and Write until context is cancelled | ||
| type hangingConn struct { | ||
| net.Conn | ||
| ctx context.Context | ||
| } | ||
|
|
||
| func (h hangingConn) Read(b []byte) (n int, err error) { | ||
| <-h.ctx.Done() | ||
| return 0, h.ctx.Err() | ||
| } | ||
|
|
||
| func (h hangingConn) Write(b []byte) (n int, err error) { | ||
| <-h.ctx.Done() | ||
| return 0, h.ctx.Err() | ||
| } | ||
|
|
||
| func TestTLSHandshakeHang(t *testing.T) { | ||
| // create a pipe to simulate a connection | ||
| // we wrap client side to ensure it hangs if it tries to read/write | ||
| clientConn, _ := net.Pipe() | ||
| defer clientConn.Close() | ||
|
|
||
| // Context to control the hanging connection's lifecycle | ||
| // This ensures the goroutine spawned by Handshake eventually exits | ||
| connCtx, connCancel := context.WithCancel(context.Background()) | ||
| defer connCancel() | ||
|
|
||
| hanging := hangingConn{ | ||
| Conn: clientConn, | ||
| ctx: connCtx, | ||
| } | ||
|
|
||
| // create a tls connection using the hanging connection | ||
| // we don't need a real server because we want to test the client-side timeout | ||
| // when the "network" hangs during handshake hello or similar. | ||
| config := &tls.Config{ | ||
| InsecureSkipVerify: true, | ||
| } | ||
| tlsConn := tls.Client(hanging, config) | ||
|
|
||
| // Create a dummy client just to call the method | ||
| client := &Client{} | ||
|
|
||
| // context with short timeout for the handshake operation | ||
| ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) | ||
| defer cancel() | ||
|
|
||
| start := time.Now() | ||
| err := client.tlsHandshakeWithTimeout(tlsConn, ctx) | ||
| duration := time.Since(start) | ||
|
|
||
| // Check if it respected timeout | ||
| if duration > 1*time.Second { | ||
| t.Errorf("Handshake took too long: %v, expected ~200ms", duration) | ||
| } | ||
|
|
||
| if err == nil { | ||
| t.Error("Expected timeout error, got nil") | ||
| } else { | ||
| // verify it's the right error | ||
| if !strings.Contains(err.Error(), "timeout") { | ||
| t.Errorf("Expected timeout error, got: %v", err) | ||
| } | ||
| } | ||
|
Comment on lines
+29
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Goroutine leak in test: the spawned handshake goroutine can never be unblocked.
Also, the server side of A simple fix: use a channel to make Suggested fix-type hangingConn struct {
- net.Conn
+type hangingConn struct {
+ net.Conn
+ done chan struct{}
}
-func (h hangingConn) Read(b []byte) (n int, err error) {
- select {}
+func (h hangingConn) Read(b []byte) (n int, err error) {
+ <-h.done
+ return 0, net.ErrClosed
}
-func (h hangingConn) Write(b []byte) (n int, err error) {
- select {}
+func (h hangingConn) Write(b []byte) (n int, err error) {
+ <-h.done
+ return 0, net.ErrClosed
}
func TestTLSHandshakeHang(t *testing.T) {
- clientConn, _ := net.Pipe()
+ clientConn, serverConn := net.Pipe()
defer clientConn.Close()
+ defer serverConn.Close()
- hanging := hangingConn{Conn: clientConn}
+ done := make(chan struct{})
+ defer close(done) // unblocks the goroutine after test assertions
+ hanging := hangingConn{Conn: clientConn, done: done}🧰 Tools🪛 ast-grep (0.40.5)[warning] 36-38: MinVersion (missing-ssl-minversion-go) 🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dogancanbakir I have refactored the test case (ztls_timeout_test.go) to address the goroutine leak noted by CodeRabbit. The test still passes successfully. Ready for final review. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why spawn a goroutine?