SSH连接超时

huangapple go评论68阅读模式
英文:

SSH Connection Timeout

问题

我正在尝试使用golang.org/x/crypto/ssh进行SSH连接,但我很惊讶地发现无法找到如何设置NewSession函数的超时时间(实际上,我没有看到任何设置超时的方法)。当我尝试连接到一个有问题的服务器时,它会一直挂起很长时间。我已经编写了一些代码,使用selecttime.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.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)

    // 这将每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.

huangapple
  • 本文由 发表于 2015年7月22日 13:00:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/31554196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定