Blob Blame History Raw
From 0d49c72d27dd400062c5632a14c4ff225de22832 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Mon, 27 Jun 2016 10:16:51 +0100
Subject: [PATCH] p2v: Fix timeout error when connecting to unresponsive ssh
 server (RHBZ#1350363).

If an ssh server is completely unresponsive (ie. it doesn't even send
back ICMP errors), then eventually ssh times out after ConnectTimeout
seconds [ConnectTimeout is an ssh option].

Unfortunately the miniexpect timeout (default 60 seconds) was less
than ConnectTimeout so we never saw the ssh error.  Instead we would
see the internal error:

  remote server closed the connection unexpectedly,
  waiting for: password prompt

This commit sets the ssh ConnectTimeout to an explicit value, and sets
the miniexpect timeout to be 20 seconds larger than ConnectTimeout, so
that in most cases we will see the ssh error message before miniexpect
times out.

Thanks: Ming Xie for finding and diagnosing the problem
(cherry picked from commit 0b836c40f2674e8632212363b9ad4f7781e5622e)
---
 p2v/ssh.c | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/p2v/ssh.c b/p2v/ssh.c
index 93c4c55..f4adde4 100644
--- a/p2v/ssh.c
+++ b/p2v/ssh.c
@@ -57,6 +57,8 @@
 #include "miniexpect.h"
 #include "p2v.h"
 
+#define SSH_TIMEOUT 60          /* seconds */
+
 char *v2v_version = NULL;
 char **input_drivers = NULL;
 char **output_drivers = NULL;
@@ -309,6 +311,7 @@ start_ssh (struct config *config, char **extra_args, int wait_prompt)
 {
   size_t i, j, nr_args, count;
   char port_str[64];
+  char connect_timeout_str[128];
   CLEANUP_FREE /* [sic] */ const char **args = NULL;
   mexp_h *h;
   const int ovecsize = 12;
@@ -328,9 +331,9 @@ start_ssh (struct config *config, char **extra_args, int wait_prompt)
     nr_args = guestfs_int_count_strings (extra_args);
 
   if (using_password_auth)
-    nr_args += 11;
-  else
     nr_args += 13;
+  else
+    nr_args += 15;
   args = malloc (sizeof (char *) * nr_args);
   if (args == NULL) {
     perror ("malloc");
@@ -346,6 +349,10 @@ start_ssh (struct config *config, char **extra_args, int wait_prompt)
   args[j++] = config->username ? config->username : "root";
   args[j++] = "-o";             /* Host key will always be novel. */
   args[j++] = "StrictHostKeyChecking=no";
+  args[j++] = "-o";             /* ConnectTimeout */
+  snprintf (connect_timeout_str, sizeof connect_timeout_str,
+            "ConnectTimeout=%d", SSH_TIMEOUT);
+  args[j++] = connect_timeout_str;
   if (using_password_auth) {
     /* Only use password authentication. */
     args[j++] = "-o";
@@ -366,12 +373,30 @@ start_ssh (struct config *config, char **extra_args, int wait_prompt)
   args[j++] = NULL;
   assert (j == nr_args);
 
+#if DEBUG_STDERR && 0
+  fputs ("ssh command: ", stderr);
+  for (i = 0; i < nr_args - 1; ++i) {
+    if (i > 0) fputc (' ', stderr);
+    fputs (args[i], stderr);
+  }
+  fputc ('\n', stderr);
+#endif
+
+  /* Create the miniexpect handle. */
   h = mexp_spawnv ("ssh", (char **) args);
   if (h == NULL) {
     set_ssh_internal_error ("ssh: mexp_spawnv: %m");
     return NULL;
   }
 
+  /* We want the ssh ConnectTimeout to be less than the miniexpect
+   * timeout, so that if the server is completely unresponsive we
+   * still see the error from ssh, not a timeout from miniexpect.  The
+   * obvious solution to this is to set ConnectTimeout (above) and to
+   * set the miniexpect timeout to be a little bit larger.
+   */
+  mexp_set_timeout (h, SSH_TIMEOUT + 20);
+
   if (using_password_auth &&
       config->password && strlen (config->password) > 0) {
     CLEANUP_FREE char *ssh_message = NULL;
-- 
1.8.3.1