From 8228251a7dbafd61cad0435ebc41a0a53dc2981e Mon Sep 17 00:00:00 2001 From: appleboy Date: Fri, 10 Oct 2025 19:29:21 +0800 Subject: [PATCH] feat: improve proxy SSH client management and configuration - Add settings.local.json file to configure Claude permissions - Ensure proxy SSH client connections are closed on timeout, error, or after successful connection in Connect method - Add a test to verify proxy clients are properly cleaned up during multiple Connect attempts fix https://github.com/appleboy/easyssh-proxy/issues/88 Signed-off-by: appleboy --- .claude/settings.local.json | 9 +++++++++ easyssh.go | 6 ++++++ easyssh_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 .claude/settings.local.json 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") + } +}