SFTP: ssh.ClientConfig.Timeout is never set, leaving connection attempts unbounded
createSSHConfig builds the client config with only User, Auth and HostKeyCallback, leaving Timeout at its zero value. ssh.Dial passes that straight to net.DialTimeout, where zero means no timeout, so each connection attempt falls back to the kernel's tcp_syn_retries
(~127s with the default of 6).
That cost is then multiplied twice over:
connection.UsingConnectionretries the dial throughretry.WithExponentialBackoff, withmaxAttempts = 10retryingStoragewraps every blob operation in another 10 attempts
So a single blob operation against an unreachable SFTP host can spend roughly 100 x 127s (~3.5 hours) before giving up, and a snapshot performs many of them.
Reproduction
Tested with kopia 0.23.1 (build 72ec08fd8edb86c67ed27099bf1b955e1f308ffa) on Linux 6.8.
- Create an SFTP repository against a reachable host.
- Make the server unreachable (drop, do not reject):
iptables -A OUTPUT -p tcp --dport 22 -j DROP kopia snapshot create <dir>
Observed: the command does not terminate. In my test it was still running after two hours, repeatedly logging
connection failed: error establishing connecting: unable to dial [127.0.0.1:22]:
&ssh.ClientConfig{... Timeout:0}: dial tcp 127.0.0.1:22: connect: connection timed out, will retryNote Timeout:0 in the dumped config.
Expected: the operation fails within a bounded, ideally configurable, period.
Suggested fix
Add a connect-timeout option to sftp.Options, plumb it into ssh.ClientConfig.Timeout, and give it a non-zero default. The external SSH path can already get this through -o ConnectTimeout= in --ssh-args; the native Go path has no equivalent.
Note
Making this bounded matters more than it might appear, because the run also cannot be stopped with SIGTERM while it is in this state (the terminate handler sets a cooperative flag that the retry loop never reads), so timeout, a manual kill, and service shutdown all fail to interrupt it. See my comment here.
Source: kopia/kopia