英文:
SSH Connection Timeout
问题
我正在尝试使用golang.org/x/crypto/ssh
进行SSH连接,但我很惊讶地发现无法找到如何设置NewSession
函数的超时时间(实际上,我没有看到任何设置超时的方法)。当我尝试连接到一个有问题的服务器时,它会一直挂起很长时间。我已经编写了一些代码,使用select
和time.After
来实现超时,但感觉像是一种折中的方法。还有一种方法是在我的结构体中保持底层的net.Conn
连接,并不断调用Conn.SetDeadline()
方法。我还没有尝试这种方法,因为我不知道crypto/ssh
库是否会覆盖这个方法或其他类似的方法。
有没有人知道如何在这个库中设置超时时间来处理无响应的服务器?或者有没有其他更好的库可以推荐?
英文:
I am trying to make SSH connections using golang.org/x/crypto/ssh
and I am kinda surprised that I can't seem to find out how to timeout the NewSession
function (I actually don't seen any way to timeout anything). When I try to connect to a server that is having issues, this just hangs for a very long time. I have written something to use select
with a time.After
but it just feels like a hack. Something I haven't tried yet is to keep the underlying net.Conn
in my struct and just keep doing Conn.SetDeadline()
calls. Haven't tried this yet because I don't know if the crypto/ssh library overrides this or anything like that.
Anyone have a good way to timeout dead servers with this library? Or does anyone know of a better library?
答案1
得分: 20
一种透明处理此问题的方法是使用ssh包创建一个带有空闲超时的连接,通过自定义的net.Conn
设置截止时间。然而,这会导致连接上的后台读取超时,所以我们需要使用ssh keepalives来保持连接打开。根据您的用例,仅使用ssh keepalives作为连接断开的警报可能已经足够。
// Conn包装了net.Conn,并为每个读取和写入操作设置截止时间。
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
或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)
// 这将每2秒发送一次keepalive数据包
// 对于这些数据包,没有有用的响应,所以如果出现错误,我们可以直接中止
go func() {
t := time.NewTicker(2 * time.Second)
defer t.Stop()
for range t.C {
_, _, err := client.Conn.SendRequest("keepalive@golang.org", true, nil)
if err != nil {
return
}
}
}()
return client, nil
}
英文:
One way to handle this transparently with the ssh package, is to create a connection with an idle timeout via a custom net.Conn
which sets deadlines for you. However, this will cause the background Reads on a connection to timeout, so we need to use ssh keepalives to keep the connection open. Depending on your use case, simply using ssh keepalives as an alert for a dead connection may suffice.
// 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)
}
You can then use net.DialTimeout
or a net.Dialer
to get the connection, wrap it in your Conn
with timeouts, and pass it into 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 range t.C {
_, _, err := client.Conn.SendRequest("keepalive@golang.org", true, nil)
if err != nil {
return
}
}
}()
return client, nil
}
答案2
得分: 9
在ssh.ClientConfig.Timeout
上设置超时时间。
cfg := ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
Timeout: 15 * time.Second, // 最大建立连接时间
}
ssh.Dial("tcp", ip+":22", &cfg)
当你调用ssh.Dial
时,超时时间将传递给net.DialTimeout
。
英文:
Set the timeout on the 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)
When you call ssh.Dial
, the timeout will be passed to net.DialTimeout
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论