现象
在自己的程序中使用类似下面的代码监听 SSH 的全局请求:
go
for {
select {
case <-ctx.Done():
return nil
case req, ok := <-globalReqs:
if !ok {
return nil
}
// Handle request
fmt.Println("Received global request:", req.Type)
req.Reply(true, nil)
}
}
发现发来的请求有时候没有接收到, 而对端的提示是 reject, 甚至出现 reject, 成功 如此交替出现的情况 (那很公平调度了).
其实很明显是有啥东西和自己的代码在抢同一个通道, 但是因为我一直在找发送端的代码问题, 为这个问题浪费了一晚上的生命, 甚至怀疑这库有 bug
原因
用自定义的 dialer 创建 ssh 客户端时, 代码是像这样的:
go
dialer := &net.Dialer{Timeout: 30 * time.Second}
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return err
}
sshconn, chans, globalReqs, err := ssh.NewClientConn(conn, addr, conf)
if err != nil {
return fmt.Errorf("failed to establish SSH connection: %w", err)
}
client := ssh.NewClient(sshconn, chans, reqs)
查看 ssh.NewClient
源码:
go
// NewClient creates a Client on top of the given connection.
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
conn := &Client{
Conn: c,
channelHandlers: make(map[string]chan NewChannel, 1),
}
go conn.handleGlobalRequests(reqs)
go conn.handleChannelOpens(chans)
go func() {
conn.Wait()
conn.forwards.closeAll()
}()
return conn
}
可以看到它用 handleGlobalRequests
来 handle 了全局请求通道, 这个函数是:
go
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
for r := range incoming {
// This handles keepalive messages and matches
// the behaviour of OpenSSH.
r.Reply(false, nil)
}
}
破案 😇
解决
主要是 ssh.NewClientConn(conn, addr, conf)
紧接着就是 ssh.NewClient
带来了点迷惑性, 前者的返回正好是后者所需的所有参数.
给 ssh.NewClient
的 <- chan *Request
传个 nil
就行了
Q.E.D.