2015-07-22 184 views
10

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

任何人都有一个很好的方法来超时死亡的服务器与这个库?还是有人知道更好的图书馆?

+0

是的,我认为使用'Conn'' SetDeadline()'做'NewClient()'是Way(tm)。评论不回答'因为我对ssh'软件包没有特别的了解。 'grep -R Deadline src/golang.org/x/crypto/ssh'的输出让我觉得这个包不会妨碍你,但不会为你做;唯一的提及与ssh端口转发相关。 – twotwotwo

+0

是''SetDeadline()'的作品,但我得说一些类似超时缺失的图书馆意味着作出连接似乎是一个主要的疏忽..不要发出忘恩负义或任何东西,也许如果我有时间,我会看到如果我可以帮忙包括它 – user3591723

+0

对我来说有点陌生的事情是,当我建立一个连接到我认识的一台服务器时,我知道已经死亡/有问题(我无法从终端进入ssh)我根本不会从ssh.NewClientConn中得到错误,也不会返回一个零客户端。无论如何,这是我的问题的主要原因,它试图用一个活着传递的死亡服务器调用'NewSession'。 – user3591723

回答

14

通过ssh软件包透明地处理这种情况的一种方法是通过自定义net.Conn创建一个空闲超时连接,该连接为您设置了最后期限。但是,这会导致连接的后台读取超时,所以我们需要使用ssh keepalives来保持连接处于打开状态。根据您的使用情况,仅使用ssh keepalives作为死连接的警报就足够了。

// 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.DialTimeoutnet.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("[email protected]", true, nil) 
      if err != nil { 
       return 
      } 
     } 
    }() 
    return client, nil 
} 
+0

这绝对会让你更明白你在做什么,而不是我想出来的。可能会实现这样的事情。 – user3591723

0

设置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