From a351484997b84682af72412f08e49afd029376cb Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 15 Nov 2021 12:24:31 +0000 Subject: [PATCH] sftp: fix timeout on hashing large files by sending keepalives Before this fix the SFTP sessions could timeout when doing hashes if they took longer than the --timeout parameter. This patch sends keepalive packets every minute while a shell command is running to keep the connection open. See: https://forum.rclone.org/t/rclone-check-over-sftp-failure-to-calculate-md5-hash-for-large-files/27487 --- backend/sftp/sftp.go | 32 +++++++++++++++++++++++++++++++- docs/content/sftp.md | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/backend/sftp/sftp.go b/backend/sftp/sftp.go index ebdc4a24c..7df0f5286 100644 --- a/backend/sftp/sftp.go +++ b/backend/sftp/sftp.go @@ -42,7 +42,8 @@ const ( hashCommandNotSupported = "none" minSleep = 100 * time.Millisecond maxSleep = 2 * time.Second - decayConstant = 2 // bigger for slower decay, exponential + decayConstant = 2 // bigger for slower decay, exponential + keepAliveInterval = time.Minute // send keepalives every this long while running commands ) var ( @@ -339,6 +340,32 @@ func (c *conn) wait() { c.err <- c.sshClient.Conn.Wait() } +// Send a keepalive over the ssh connection +func (c *conn) sendKeepAlive() { + _, _, err := c.sshClient.SendRequest("keepalive@openssh.com", true, nil) + if err != nil { + fs.Debugf(nil, "Failed to send keep alive: %v", err) + } +} + +// Send keepalives every interval over the ssh connection until done is closed +func (c *conn) sendKeepAlives(interval time.Duration) (done chan struct{}) { + done = make(chan struct{}) + go func() { + t := time.NewTicker(interval) + defer t.Stop() + for { + select { + case <-t.C: + c.sendKeepAlive() + case <-done: + return + } + } + }() + return done +} + // Closes the connection func (c *conn) close() error { sftpErr := c.sftpClient.Close() @@ -1098,6 +1125,9 @@ func (f *Fs) run(ctx context.Context, cmd string) ([]byte, error) { } defer f.putSftpConnection(&c, err) + // Send keepalives while the connection is open + defer close(c.sendKeepAlives(keepAliveInterval)) + session, err := c.sshClient.NewSession() if err != nil { return nil, fmt.Errorf("run: get SFTP session: %w", err) diff --git a/docs/content/sftp.md b/docs/content/sftp.md index fc1e5fad8..8236afb3c 100644 --- a/docs/content/sftp.md +++ b/docs/content/sftp.md @@ -620,7 +620,7 @@ issue](https://github.com/pkg/sftp/issues/156) is fixed. Note that since SFTP isn't HTTP based the following flags don't work with it: `--dump-headers`, `--dump-bodies`, `--dump-auth` -Note that `--timeout` isn't supported (but `--contimeout` is). +Note that `--timeout` and `--contimeout` are both supported. ## C14 {#c14}