From b94e8bc4cabf3ced8e39b95876b957fe0de2761b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Fri, 12 Jun 2026 03:58:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SSH=20=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=20goroutine=20=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssh.NewClientConn 不接受 context,context 取消后底层 TCP 连接未关闭, 导致 readLoop goroutine 永久阻塞在 conn.Read 上。大规模扫描时泄漏数万 goroutine。 - doSSHAuth 新增 goroutine 监听 context 取消并关闭底层连接 - TestSingleCredential 移除 5 秒超时放弃逻辑,改为持续等待清理 --- plugins/services/credential_tester.go | 16 +++++----------- plugins/services/ssh.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/plugins/services/credential_tester.go b/plugins/services/credential_tester.go index 8fb2ca4..8066aac 100644 --- a/plugins/services/credential_tester.go +++ b/plugins/services/credential_tester.go @@ -79,18 +79,12 @@ func TestSingleCredential(ctx context.Context, cred Credential, authFn AuthFunc) case result := <-resultChan: return result case <-ctx.Done(): - // context 被取消,但 authFn goroutine 可能还阻塞在第三方库 IO 上 - // 限时等待:超过 5 秒直接放弃,避免 goroutine 无限泄漏 + // context 被取消,等待 authFn goroutine 返回并清理连接 + // 各插件的 authFn 应在 context 取消时关闭底层连接使 goroutine 快速退出 go func() { - timer := time.NewTimer(5 * time.Second) - defer timer.Stop() - select { - case result := <-resultChan: - if result != nil && result.Conn != nil { - _ = result.Conn.Close() - } - case <-timer.C: - // 第三方库不响应取消,放弃等待 + result := <-resultChan + if result != nil && result.Conn != nil { + _ = result.Conn.Close() } }() return &AuthResult{ diff --git a/plugins/services/ssh.go b/plugins/services/ssh.go index cc2f81e..43468ec 100644 --- a/plugins/services/ssh.go +++ b/plugins/services/ssh.go @@ -122,10 +122,28 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C } } + // 监听 context 取消,强制关闭底层连接以中断 SSH 握手和 readLoop + done := make(chan struct{}) + defer close(done) + go func() { + select { + case <-ctx.Done(): + _ = conn.Close() + case <-done: + } + }() + // 在TCP连接上创建SSH客户端 sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig) if err != nil { _ = conn.Close() + if ctx.Err() != nil { + return &AuthResult{ + Success: false, + ErrorType: ErrorTypeNetwork, + Error: ctx.Err(), + } + } return &AuthResult{ Success: false, ErrorType: classifySSHErrorType(err),