在Go中设置idletimeout

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

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(&amp;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(&quot;SSH: Handshaking was terminated&quot;, log.Fields{
    				&quot;address&quot;: rAddr,
    				&quot;error&quot;:   err,
    				&quot;session&quot;: session})
    		} else {
    			log.Errorw(&quot;SSH: Error on handshaking&quot;, log.Fields{
    				&quot;address&quot;: rAddr,
    				&quot;error&quot;:   err,
    				&quot;session&quot;: session})
    		}
    
    		atomic.AddInt64(&amp;stats.Stats.AuthorizationFailed, 1)
    		return
    	}
    
    	log.Infow(&quot;connection accepted&quot;, log.Fields{
    		&quot;user&quot;: sConn.User(),
    	})
    
    	if user, ok := users[session]; ok {
    		log.Infow(&quot;SSH: Connection accepted&quot;, log.Fields{
    			&quot;user&quot;:          user.LogFields(),
    			&quot;clientVersion&quot;: string(sConn.ClientVersion())})
    
    		atomic.AddInt64(&amp;stats.Stats.AuthorizationSucceeded, 1)
            
    		// The incoming Request channel must be serviced.
    		go ssh.DiscardRequests(reqs)
    
    		// Key ID: sConn.Permissions.Extensions[&quot;key-id&quot;]
    		handleServerConn(user, chans)
    
    		log.Infow(&quot;connection finished&quot;, log.Fields{&quot;user&quot;: user.LogFields()})
            
    		log.Infow(&quot;checking connections&quot;, log.Fields{
    			//&quot;cc&quot;:          Stats.AcceptedConnections,
    			&quot;cc2&quot;: &amp;stats.Stats.AcceptedConnections})
    
    		// Remove connection from local cache
    		delete(users, session)
    
    	} else {
    		log.Infow(&quot;user not found from memory&quot;, log.Fields{&quot;username&quot;: sConn.User()})
    	}
    
    }


This code is coming from the Listen function:

    func Listen() {
       
    
    	listener, err := net.Listen(&quot;tcp&quot;, sshListen)
    	if err != nil {
    		panic(err)
    	}
        
    
    	if useProxyProtocol {
    		listener = &amp;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(&quot;SSH: Error accepting incoming connection&quot;, log.Fields{&quot;error&quot;: err})
    			atomic.AddInt64(&amp;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&#39;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 := &amp;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 &quot;Connection closed&quot; when I try to do ssh. Is it still waiting for &quot;conn&quot; variable in the function call?


</details>


# 答案1
**得分**: 1

&gt; 是否可能仅为空闲超过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 := &amp;SshProxyConn{conn, time.Second * 20}
Conn, chans, reqs, err := ssh.NewServerConn(sshproxyconn , config)

huangapple
  • 本文由 发表于 2022年9月22日 19:56:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/73814237.html
匿名

发表评论

匿名网友

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

确定