diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..cd4262a --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "mcp__github__get_issue" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/easyssh.go b/easyssh.go index 233313d..5d3e2fa 100644 --- a/easyssh.go +++ b/easyssh.go @@ -292,18 +292,24 @@ func (ssh_conf *MakeConfig) Connect() (*ssh.Session, *ssh.Client, error) { conn = result.conn err = result.err case <-ctx.Done(): + proxyClient.Close() return nil, nil, fmt.Errorf("%w: %v", ErrProxyDialTimeout, ctx.Err()) } if err != nil { + proxyClient.Close() return nil, nil, err } ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), targetConfig) if err != nil { + proxyClient.Close() return nil, nil, err } + // Close the proxy client after successfully establishing the target connection + // The target connection (ncc) is now independent of the proxy client + proxyClient.Close() client = ssh.NewClient(ncc, chans, reqs) } else { client, err = ssh.Dial(string(ssh_conf.Protocol), net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), targetConfig) diff --git a/easyssh_test.go b/easyssh_test.go index ec3edeb..ca470ce 100644 --- a/easyssh_test.go +++ b/easyssh_test.go @@ -666,3 +666,27 @@ func TestProxyGoroutineLeak(t *testing.T) { "Goroutine leak detected: initial=%d, final=%d", initialGoroutines, finalGoroutines) } +// TestProxyClientCleanup tests that proxy clients are properly closed during multiple Connect calls +func TestProxyClientCleanup(t *testing.T) { + ssh := &MakeConfig{ + Server: "10.255.255.1", // Non-routable IP + User: "testuser", + Port: "22", + KeyPath: "./tests/.ssh/id_rsa", + Timeout: 500 * time.Millisecond, // Short timeout + Proxy: DefaultConfig{ + User: "testuser", + Server: "10.255.255.2", // Another non-routable IP for proxy + Port: "22", + KeyPath: "./tests/.ssh/id_rsa", + Timeout: 500 * time.Millisecond, + }, + } + + // Test multiple connect attempts - they should all fail gracefully without leaking resources + for i := 0; i < 3; i++ { + _, _, err := ssh.Connect() + // Should have error due to non-routable IP, but no resource leaks + assert.NotNil(t, err, "Connect should fail with timeout/connection error") + } +}