问题 SSH连接超时


我正在尝试使用SSH连接 golang.org/x/crypto/ssh 我有点惊讶,我似乎无法找到如何超时 NewSession 功能(我实际上没有看到任何超时的方法)。当我尝试连接到有问题的服务器时,这只会挂起很长一段时间。我写了一些东西要用 select 用一个 time.After 但它只是感觉像一个黑客。我还没有尝试的东西是保持潜在的 net.Conn 在我的结构中,只是继续做 Conn.SetDeadline() 调用。还没有尝试过,因为我不知道crypto / ssh库是否覆盖了这个或类似的东西。

任何人都有一个很好的方法来使这个库超时死机服务器?或者有人知道更好的图书馆吗?


3359
2017-07-22 05:00


起源

是的,我觉得这样做 NewClient() 用一个 Conn 您 SetDeadline() on the Way(tm)。评论没有回答'因为我没有特别熟悉的 ssh 包。的输出 grep -R Deadline src/golang.org/x/crypto/ssh 让我觉得包裹不会妨碍你,但不会为你做;唯一提及似乎与ssh端口转发相关。 - twotwotwo
是的 SetDeadline() 工作,但我得说一个像图书馆的超时缺失意味着建立联系似乎是一个主要的疏忽..不要听起来忘恩负义或任何事情,也许如果我有时间我会看到我是否可以帮助包括它 - user3591723
对我来说有点奇怪的是,当我建立与我的一台服务器的连接时,我知道它已经死了/有问题(我不能从终端进入它)我没有得到一个来自的错误 ssh.NewClientConn 根本不也没有返回零客户端。这是我的问题的主要原因,它正试图打电话 NewSession 一个死的服务器正在传递。 - user3591723
如果 ssh.NewClientConn 返回ok,那么服务器并没有真正死掉,因为它可以完成ssh握手。 - JimB
@ user3591723 - 我认为理念是如果你可以使用另一个标准包来实现一项功能,stdlib作者通常不会提供另一种方法来做到这一点,即使更方便。例如,人们经常使用Go's net/http 编写返回JSON的服务,你可以设想方便的方法来使它更容易构建,但是 net/http (独自)不提供; net/http 必须联系到 encoding/json 与其他代码。 - twotwotwo


答案:


使用ssh包透明地处理此问题的一种方法是通过自定义创建具有空闲超时的连接 net.Conn 这为你设定了截止日期。但是,这会导致连接上的后台读取超时,因此我们需要使用ssh keepalive来保持连接打开。根据您的使用情况,只需使用ssh keepalive作为死连接的警报就足够了。

// Conn wraps a net.Conn, and sets a deadline for every read
// and write operation.
type Conn struct {
    net.Conn
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
}

func (c *Conn) Read(b []byte) (int, error) {
    err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
    if err != nil {
        return 0, err
    }
    return c.Conn.Read(b)
}

func (c *Conn) Write(b []byte) (int, error) {
    err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
    if err != nil {
        return 0, err
    }
    return c.Conn.Write(b)
}

然后你可以使用 net.DialTimeout 或者a net.Dialer 要获得连接,请将其包装在您的 Conn 超时,并将其传递给 ssh.NewClientConn

func SSHDialTimeout(network, addr string, config *ssh.ClientConfig, timeout time.Duration) (*ssh.Client, error) {
    conn, err := net.DialTimeout(network, addr, timeout)
    if err != nil {
        return nil, err
    }

    timeoutConn := &Conn{conn, timeout, timeout}
    c, chans, reqs, err := ssh.NewClientConn(timeoutConn, addr, config)
    if err != nil {
        return nil, err
    }
    client := ssh.NewClient(c, chans, reqs)

    // this sends keepalive packets every 2 seconds
    // there's no useful response from these, so we can just abort if there's an error
    go func() {
        t := time.NewTicker(2 * time.Second)
        defer t.Stop()
        for {
            <-t.C
            _, _, err := client.Conn.SendRequest("keepalive@golang.org", true, nil)
            if err != nil {
                return
            }
        }
    }()
    return client, nil
}

15
2017-07-22 14:38



这肯定会让你比我想出的更清楚你正在做什么。可能会实现这样的事情。 - user3591723


设置超时 ssh.ClientConfig

cfg := ssh.ClientConfig{
    User: "root",
    Auth: []ssh.AuthMethod{
        ssh.PublicKeys(signer),
    },
    HostKeyCallback: ssh.FixedHostKey(hostKey),
    Timeout:         15 * time.Second, // max time to establish connection
}

ssh.Dial("tcp", ip+":22", &cfg)

你打电话时 ssh.Dial,超时将传递给 net.DialTimeout


1
2017-08-19 04:28