在Golang中正确关闭加密SSH会话并释放所有资源的方法是什么?

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

Proper way to close a crypto ssh session freeing all resources in golang?

问题

TL;DR - 如何正确关闭golang.org/x/crypto/ssh会话以释放所有资源?

我目前的调查结果如下:

golang.org/x/crypto/ssh*Session有一个Close()函数,它调用*ChannelClose()函数发送一个消息(我猜是发送给远程服务器)来关闭连接,但我没有看到关闭其他资源(如从*SessionStdoutPipe()函数返回的管道)的相关信息。

查看*SessionWait()代码,我发现*Session stdinPipeWriter被关闭了,但没有关于stdoutPipe的相关内容。

这个包感觉很像os/exec包,后者保证使用os/exec Wait()函数会清理所有资源。在轻微的调查中,我发现了Wait()函数中的一些相似之处。两者都使用以下结构来报告io.Copy调用到stdout、stderr、stdin读写器的错误(如果我理解正确,实际上只有一个错误)- 加密包示例:

var copyError error
for _ = range s.copyFuncs {
	if err := <-s.errors; err != nil && copyError == nil {
		copyError = err
	}
}

但是os/execWait()还调用了这个关闭描述符方法:

c.closeDescriptors(c.closeAfterWait)

它只是在io.Closer接口的切片上调用了关闭方法:

func (c *Cmd) closeDescriptors(closers []io.Closer) {
    for _, fd := range closers {
	    fd.Close()
    }
}

os/exec创建管道时,它会跟踪需要关闭的内容:

func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
if c.Stdout != nil {
	return nil, errors.New("exec: Stdout already set")
}
if c.Process != nil {
	return nil, errors.New("exec: StdoutPipe after process started")
}
pr, pw, err := os.Pipe()
if err != nil {
	return nil, err
}
c.Stdout = pw
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
}

在这个过程中,我注意到x/crypto/ssh*Session StdoutPipe()返回一个io.Reader,而os/exec返回一个io.ReadCloser。而且x/crypto/ssh没有跟踪需要关闭的内容。我在库中找不到对os.Pipe()的调用,所以也许实现方式不同,我可能遗漏了一些东西,并且被Pipe名称所迷惑。

英文:

TL;DR - What is the proper way to close a golang.org/x/crypto/ssh session freeing all resources?

My investigation thus far:

The golang.org/x/crypto/ssh *Session has a Close() function which calls the *Channel Close() function which sends a message (I'm guessing to the remote server) to close, but I don't see anything about closing other resources like the pipe returned from the *Session StdoutPipe() function.

Looking at the *Session Wait() code, I see that the *Session stdinPipeWriter is closed but nothing about the stdoutPipe.

This package feels a lot like the os/exec package which guarantees that using the os/exec Wait() function will clean up all the resources. Doing some light digging there shows some similarities in the Wait() functions. Both use the following construct to report errors on io.Copy calls to their stdout, stderr, stdin readers/writers (well if I'm reading this correctly actually only one error) - crypto package shown:

var copyError error
for _ = range s.copyFuncs {
	if err := &lt;-s.errors; err != nil &amp;&amp; copyError == nil {
		copyError = err
	}
}

But the os/exec Wait() also calls this close descriptor method

c.closeDescriptors(c.closeAfterWait)

which is just calling the close method on a slice of io.Closer interfaces:

func (c *Cmd) closeDescriptors(closers []io.Closer) {
    for _, fd := range closers {
	    fd.Close()
    }
}

when os/exec creates the pipe, it tracks what needs closing:

func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
if c.Stdout != nil {
	return nil, errors.New(&quot;exec: Stdout already set&quot;)
}
if c.Process != nil {
	return nil, errors.New(&quot;exec: StdoutPipe after process started&quot;)
}
pr, pw, err := os.Pipe()
if err != nil {
	return nil, err
}
c.Stdout = pw
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
}

During this I noticed that x/cyrpto/ssh *Session StdoutPipe() returns an io.Reader and ox/exec returns an io.ReadCloser. And x/crypto/ssh does not track what to close. I can't find a call to os.Pipe() in the library so maybe the implementation is different and I'm missing something and confused by the Pipe name.

答案1

得分: 4

一个会话可以通过调用Close()来关闭。没有涉及文件描述符,也没有调用os.Pipe,因为从Session.StdOutPipe返回的“pipe”只是一个概念上的管道,类型为ssh.Channel。Go通道不需要关闭,因为关闭通道不是一种清理操作,而只是向通道发送的一种消息类型。在ssh传输中,只涉及一个网络连接。

你唯一需要关闭的资源是网络连接;没有其他系统资源需要释放。调用Close()ssh.Client上将调用ssh.Conn.Close,进而关闭net.Conn

如果你需要处理网络连接,可以跳过ssh.Dial便利函数,自己拨号网络连接:

c, err := net.DialTimeout(network, addr, timeout)
if err != nil {
    return nil, err
}
conn, chans, reqs, err := ssh.NewClientConn(c, addr, config)
if err != nil {
    return nil, err
}

// 调用conn.Close将关闭底层的net.Conn

client := ssh.NewClient(c, chans, reqs)
英文:

A session is closed by calling Close(). There are no file descriptors involved, nor are there any calls to os.Pipe as the "pipe" returned from Session.StdOutPipe is only a pipe in concept and is of type ssh.Channel. Go channels don't need to be closed, because closing a channel is not a cleanup operation, rather it's simply a type of message sent to the channel. There is only ever one network connection involved in the ssh transport.

The only resource you need to close is the network connection; there are no other system resources to be freed. Calling Close() on the ssh.Client will call ssh.Conn.Close, and in turn close the net.Conn.

If you need the handle the network connection, you can always skip the ssh.Dial convenience function and Dial the network connection yourself:

c, err := net.DialTimeout(network, addr, timeout)
if err != nil {
	return nil, err
}
conn, chans, reqs, err := ssh.NewClientConn(c, addr, config)
if err != nil {
	return nil, err
}

// calling conn.Close will close the underlying net.Conn

client := ssh.NewClient(c, chans, reqs)

huangapple
  • 本文由 发表于 2017年3月4日 07:47:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/42590308.html
匿名

发表评论

匿名网友

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

确定