Тайм-аут соединения SSH

Я пытаюсь подключиться к SSH-соединениям с помощью golang.org/x/crypto/ssh, и я удивлен, что я не могу понять, как отключить функцию NewSession (я на самом деле не вижу никакого способа тайм-аута что-либо). Когда я пытаюсь подключиться к серверу с проблемами, это просто зависает в течение очень долгого времени. Я написал что-то, чтобы использовать select с time.After, но это просто похоже на хак. Что-то, чего я еще не пробовал, это сохранить базовую net.Conn в моей структуре и просто продолжать делать вызовы Conn.SetDeadline(). Не пробовал это еще, потому что я не знаю, переопределяет ли библиотека крипто /ssh это или что-то в этом роде.

У кого-нибудь есть хороший способ тайм-аута мертвых серверов с этой библиотекой? Или кто-нибудь знает лучшую библиотеку?

Ответ 1

Один из способов прозрачного управления этим пакетом ssh - создать соединение с тайм-аутом простоя через пользовательскую net.Conn который устанавливает для вас сроки. Однако это приведет к тому, что фон Reads будет подключен к таймауту, поэтому нам нужно использовать ssh keepalives, чтобы открыть соединение. В зависимости от вашего варианта использования достаточно использовать ssh keepalives в качестве оповещения о мертвом соединении.

// 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)
}

Затем вы можете использовать net.DialTimeout или net.Dialer чтобы получить соединение, обернуть его в свой Conn с тайм- ssh.NewClientConn и передать его в 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 no useful response from these, so we can just abort if there an error
    go func() {
        t := time.NewTicker(2 * time.Second)
        defer t.Stop()
        for range t.C {
            _, _, err := client.Conn.SendRequest("[email protected]", true, nil)
            if err != nil {
                return
            }
        }
    }()
    return client, nil
}

Ответ 2

Установите таймаут на 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)

Когда вы вызываете ssh.Dial, таймаут будет передан net.DialTimeout.