英文:
Setting idletimeout in Go
问题
我在Go语言中有一个处理通过TCP传入并通过SSH处理的连接的函数。我正在尝试通过在连接函数中创建结构体来设置空闲超时时间。
用例 - 客户应该能够建立连接并上传/下载多个文件
参考 - https://stackoverflow.com/questions/47912263/idletimeout-in-tcp-server
函数代码:
type Conn struct {
    net.Conn
    idleTimeout time.Duration
}
func HandleConn(conn net.Conn) {
    var err error
    rAddr := conn.RemoteAddr()
    session := shortuuid.New()
    config := LoadSSHServerConfig(session)
    blocklistItem := blocklist.GetBlockListItem(rAddr)
    if blocklistItem.IsBlocked() {
        conn.Close()
        atomic.AddInt64(&stats.Stats.BlockedConnections, 1)
        return
    }
    func (c *Conn) Read(b []byte) (int, error) {
        err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
        if err != nil {
            return 0, err
        }
        return c.Conn.Read(b)
    }
    sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
    if err != nil {
        if err == io.EOF {
            log.Errorw("SSH: Handshaking was terminated", log.Fields{
                "address": rAddr,
                "error":   err,
                "session": session})
        } else {
            log.Errorw("SSH: Error on handshaking", log.Fields{
                "address": rAddr,
                "error":   err,
                "session": session})
        }
        atomic.AddInt64(&stats.Stats.AuthorizationFailed, 1)
        return
    }
    log.Infow("connection accepted", log.Fields{
        "user": sConn.User(),
    })
    if user, ok := users[session]; ok {
        log.Infow("SSH: Connection accepted", log.Fields{
            "user":          user.LogFields(),
            "clientVersion": string(sConn.ClientVersion())})
        atomic.AddInt64(&stats.Stats.AuthorizationSucceeded, 1)
        // The incoming Request channel must be serviced.
        go ssh.DiscardRequests(reqs)
        // Key ID: sConn.Permissions.Extensions["key-id"]
        handleServerConn(user, chans)
        log.Infow("connection finished", log.Fields{"user": user.LogFields()})
        log.Infow("checking connections", log.Fields{
            // "cc":          Stats.AcceptedConnections,
            "cc2": &stats.Stats.AcceptedConnections})
        // Remove connection from local cache
        delete(users, session)
    } else {
        log.Infow("user not found from memory", log.Fields{"username": sConn.User()})
    }
}
这段代码来自Listen函数:
func Listen() {
    listener, err := net.Listen("tcp", sshListen)
    if err != nil {
        panic(err)
    }
    if useProxyProtocol {
        listener = &proxyproto.Listener{
            Listener:           listener,
            ProxyHeaderTimeout: time.Second * 10,
        }
    }
    for {
        // Once a ServerConfig has been configured, connections can be accepted.
        conn, err := listener.Accept()
        if err != nil {
            log.Errorw("SSH: Error accepting incoming connection", log.Fields{"error": err})
            atomic.AddInt64(&stats.Stats.FailedConnections, 1)
            continue
        }
        // Before use, a handshake must be performed on the incoming net.Conn.
        // It must be handled in a separate goroutine,
        // otherwise one user could easily block entire loop.
        // For example, user could be asked to trust server key fingerprint and hangs.
        go HandleConn(conn)
    }
}
是否可能仅为空闲超过20秒(无上传/下载)的连接设置截止时间?
编辑1:根据@LiamKelly的建议,我已经对代码进行了更改。现在代码如下:
type SshProxyConn struct {
    net.Conn
    idleTimeout time.Duration
}
func (c *SshProxyConn) Read(b []byte) (int, error) {
    err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
    if err != nil {
        return 0, err
    }
    return c.Conn.Read(b)
}
func HandleConn(conn net.Conn) {
    //与上述代码相同的行
    sshproxyconn := &SshProxyConn{nil, time.Second * 20}
    Conn, chans, reqs, err := ssh.NewServerConn(sshproxyconn, config)
    //与上述代码相同的行
}
但现在的问题是SSH无法进行。当我尝试进行SSH时,我收到错误消息"Connection closed"。它是否仍在等待函数调用中的"conn"变量?
<details>
<summary>英文:</summary>
I have a function in go which is handling connections which are coming through tcp and handled via ssh. I am trying to set an idle timeout by creating struct in the connection function. 
**Use case** - a customer should be able to make a connection and upload/download multiple files
**Reference** - https://stackoverflow.com/questions/47912263/idletimeout-in-tcp-server
Function code:
    type Conn struct {
        		net.Conn
        		idleTimeout time.Duration
        	}
    func HandleConn(conn net.Conn) {
    	var err error
    	rAddr := conn.RemoteAddr()
    	session := shortuuid.New()
    	config := LoadSSHServerConfig(session)
       
    	blocklistItem := blocklist.GetBlockListItem(rAddr)
    	if blocklistItem.IsBlocked() {
    		conn.Close()
    		atomic.AddInt64(&stats.Stats.BlockedConnections, 1)
    		return
    	}
    
    	
    	func (c *Conn) Read(b []byte) (int, error) {
    		err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
    		if err != nil {
    			return 0, err
    		}
    		return c.Conn.Read(b)
    	}
    
    	sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
    	if err != nil {
    		if err == io.EOF {
    			log.Errorw("SSH: Handshaking was terminated", log.Fields{
    				"address": rAddr,
    				"error":   err,
    				"session": session})
    		} else {
    			log.Errorw("SSH: Error on handshaking", log.Fields{
    				"address": rAddr,
    				"error":   err,
    				"session": session})
    		}
    
    		atomic.AddInt64(&stats.Stats.AuthorizationFailed, 1)
    		return
    	}
    
    	log.Infow("connection accepted", log.Fields{
    		"user": sConn.User(),
    	})
    
    	if user, ok := users[session]; ok {
    		log.Infow("SSH: Connection accepted", log.Fields{
    			"user":          user.LogFields(),
    			"clientVersion": string(sConn.ClientVersion())})
    
    		atomic.AddInt64(&stats.Stats.AuthorizationSucceeded, 1)
            
    		// The incoming Request channel must be serviced.
    		go ssh.DiscardRequests(reqs)
    
    		// Key ID: sConn.Permissions.Extensions["key-id"]
    		handleServerConn(user, chans)
    
    		log.Infow("connection finished", log.Fields{"user": user.LogFields()})
            
    		log.Infow("checking connections", log.Fields{
    			//"cc":          Stats.AcceptedConnections,
    			"cc2": &stats.Stats.AcceptedConnections})
    
    		// Remove connection from local cache
    		delete(users, session)
    
    	} else {
    		log.Infow("user not found from memory", log.Fields{"username": sConn.User()})
    	}
    
    }
This code is coming from the Listen function:
    func Listen() {
       
    
    	listener, err := net.Listen("tcp", sshListen)
    	if err != nil {
    		panic(err)
    	}
        
    
    	if useProxyProtocol {
    		listener = &proxyproto.Listener{
    			Listener:           listener,
    			ProxyHeaderTimeout: time.Second * 10,
    		}
    	}
    
    	
    	for {
    		// Once a ServerConfig has been configured, connections can be accepted.
    		conn, err := listener.Accept()
    		if err != nil {
    			log.Errorw("SSH: Error accepting incoming connection", log.Fields{"error": err})
    			atomic.AddInt64(&stats.Stats.FailedConnections, 1)
    			continue
    		}
    	
    
    		// Before use, a handshake must be performed on the incoming net.Conn.
    		// It must be handled in a separate goroutine,
    		// otherwise one user could easily block entire loop.
    		// For example, user could be asked to trust server key fingerprint and hangs.
    		go HandleConn(conn)
    	}
    }
Is that even possible to set a deadline for only the connections which have been idle for 20 secinds (no upload/downloads).
**EDIT 1** : Following @LiamKelly's suggestions, I have made the changes in the code. Now the code is like 
    type SshProxyConn struct {
        net.Conn
        idleTimeout time.Duration
    }
    
    func (c *SshProxyConn) Read(b []byte) (int, error) {
        err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
        if err != nil {
            return 0, err
        }
        return c.Conn.Read(b)
    }
    func HandleConn(conn net.Conn) {
        //lines of code as above
        sshproxyconn := &SshProxyConn{nil, time.Second * 20}
        Conn, chans, reqs, err := ssh.NewServerConn(sshproxyconn, config)
        //lines of code
    }
But now the issue is that SSH is not happening. I am getting the error "Connection closed" when I try to do ssh. Is it still waiting for "conn" variable in the function call?
</details>
# 答案1
**得分**: 1
> 是否可能仅为空闲超过20秒的连接设置截止日期?
首先,我要声明一下,我会假设`go-protoproxy`实现了我们期望的`Conn`接口。另外,正如你之前暗示的,我认为你不能将一个结构体方法放在另一个函数内(我还建议将其重命名为一个独特的名称,以避免`Conn`与`net.Conn`混淆)。
```go
type SshProxyConn struct {
	net.Conn
	idleTimeout time.Duration
}
func (c *SshProxyConn) Read(b []byte) (int, error) {
	err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Read(b)
}
func HandleConn(conn net.Conn) {
这样更清楚地显示了你的主要问题,你将普通的net.Conn传递给了SSH服务器,而不是你的包装类。所以
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
应该更改为 编辑
sshproxyconn := &SshProxyConn{conn, time.Second * 20}
Conn, chans, reqs, err := ssh.NewServerConn(sshproxyconn, config)
英文:
> Is that even possible to set a deadline for only the connections which have been idle for 20 [seconds]
Ok so first a general disclaimer, I am going to assume go-protoproxy implements the Conn interface as we would expected. Also as you hinted at before, I don't think you can put a a struct method inside another function (I also recommend renaming it something unique to prevent Conn vs net.Conn confusion).
type SshProxyConn struct {
	net.Conn
	idleTimeout time.Duration
}
func (c *SshProxyConn) Read(b []byte) (int, error) {
	err := c.Conn.SetReadDeadline(time.Now().Add(c.idleTimeout))
	if err != nil {
		return 0, err
	}
	return c.Conn.Read(b)
}
func HandleConn(conn net.Conn) {
This makes is more clear what your primary issue is, which you passed the normal net.Conn to your SSH server, not your wrapper class. So
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
should be EDIT
sshproxyconn := &SshProxyConn{conn, time.Second * 20}
Conn, chans, reqs, err := ssh.NewServerConn(sshproxyconn , config)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论