[Libguestfs] [p2v PATCH 04/10] open-code "localhost" as the listen hostname for nbdkit

Laszlo Ersek
2022-03-31T07:22:05Z

At this point, the only NBD server type virt-p2v supports is nbdkit with
socket activation; therefore, outputting "localhost" from
start_nbd_server(), and tracking it via variables / parameters from there
on is needless generality. Remove the affected variables and parameters,
such as "ipaddr", "hostname", and
"nbd_local_ipaddr".

Ref: https://listman.redhat.com/archives/libguestfs/2022-March/028475.html
Suggested-by: Richard W.M. Jones <rjones at redhat.com>
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
 conversion.c | 13 ++---
 nbd.c        | 51 +++++++++-----------
 p2v.h        |  6 +--
 ssh.c        |  6 +--
 4 files changed, 33 insertions(+), 43 deletions(-)

diff --git a/conversion.c b/conversion.c
index 8daa1d9f39fa..8bc4119fb47c 100644
--- a/conversion.c
+++ b/conversion.c
@@ -155,270 +155,267 @@ int
 start_conversion (struct config *config,
                   void (*notify_ui) (int type, const char *data))
 {
   int ret = -1;
   int status;
   size_t i, len;
   const size_t nr_disks = guestfs_int_count_strings (config->disks);
   time_t now;
   struct tm tm;
   CLEANUP_FREE struct data_conn *data_conns = NULL;
   CLEANUP_FREE char *remote_dir = NULL;
   char tmpdir[]           = "/tmp/p2v.XXXXXX";
   char name_file[]        = "/tmp/p2v.XXXXXX/name";
   char physical_xml_file[] = "/tmp/p2v.XXXXXX/physical.xml";
   char wrapper_script[]   = "/tmp/p2v.XXXXXX/virt-v2v-wrapper.sh";
   char dmesg_file[]       = "/tmp/p2v.XXXXXX/dmesg";
   char lscpu_file[]       = "/tmp/p2v.XXXXXX/lscpu";
   char lspci_file[]       = "/tmp/p2v.XXXXXX/lspci";
   char lsscsi_file[]      = "/tmp/p2v.XXXXXX/lsscsi";
   char lsusb_file[]       = "/tmp/p2v.XXXXXX/lsusb";
   char p2v_version_file[] = "/tmp/p2v.XXXXXX/p2v-version";
   int inhibit_fd = -1;

 #if DEBUG_STDERR
   print_config (config, stderr);
   fprintf (stderr, "\n");
 #endif

   set_control_h (NULL);
   set_running (1);
   set_cancel_requested (0);

   inhibit_fd = inhibit_power_saving ();
 #ifdef DEBUG_STDERR
   if (inhibit_fd == -1)
     fprintf (stderr, "warning: virt-p2v cannot inhibit power saving during
conversion.\n");
 #endif

   data_conns = malloc (sizeof (struct data_conn) * nr_disks);
   if (data_conns == NULL)
     error (EXIT_FAILURE, errno, "malloc");

   for (i = 0; config->disks[i] != NULL; ++i) {
     data_conns[i].h = NULL;
     data_conns[i].nbd_pid = 0;
     data_conns[i].nbd_remote_port = -1;
   }

   /* Start the data connections and NBD server processes, one per disk. */
   for (i = 0; config->disks[i] != NULL; ++i) {
-    const char *nbd_local_ipaddr;
     int nbd_local_port;
     CLEANUP_FREE char *device = NULL;

     if (config->disks[i][0] == '/') {
       device = strdup (config->disks[i]);
       if (!device) {
         perror ("strdup");
         cleanup_data_conns (data_conns, nr_disks);
         exit (EXIT_FAILURE);
       }
     }
     else if (asprintf (&device, "/dev/%s", config->disks[i])
== -1) {
       perror ("asprintf");
       cleanup_data_conns (data_conns, nr_disks);
       exit (EXIT_FAILURE);
     }

     if (notify_ui) {
       CLEANUP_FREE char *msg;
       if (asprintf (&msg,
                     _("Starting local NBD server for %s ..."),
                     config->disks[i]) == -1)
         error (EXIT_FAILURE, errno, "asprintf");
       notify_ui (NOTIFY_STATUS, msg);
     }

     /* Start NBD server listening on the given port number. */
-    data_conns[i].nbd_pid -      start_nbd_server (&nbd_local_ipaddr,
&nbd_local_port, device);
+    data_conns[i].nbd_pid = start_nbd_server (&nbd_local_port, device);
     if (data_conns[i].nbd_pid == 0) {
       set_conversion_error ("NBD server error: %s", get_nbd_error
());
       goto out;
     }

     /* Wait for NBD server to start up and listen. */
-    if (wait_for_nbd_server_to_start (nbd_local_ipaddr, nbd_local_port) == -1)
{
+    if (wait_for_nbd_server_to_start (nbd_local_port) == -1) {
       set_conversion_error ("NBD server error: %s", get_nbd_error
());
       goto out;
     }

     if (notify_ui) {
       CLEANUP_FREE char *msg;
       if (asprintf (&msg,
                     _("Opening data connection for %s ..."),
                     config->disks[i]) == -1)
         error (EXIT_FAILURE, errno, "asprintf");
       notify_ui (NOTIFY_STATUS, msg);
     }

     /* Open the SSH data connection, with reverse port forwarding
      * back to the NBD server.
      */
-    data_conns[i].h = open_data_connection (config,
-                                            nbd_local_ipaddr, nbd_local_port,
+    data_conns[i].h = open_data_connection (config, nbd_local_port,

&data_conns[i].nbd_remote_port);
     if (data_conns[i].h == NULL) {
       const char *err = get_ssh_error ();

       set_conversion_error ("could not open data connection over SSH to
the conversion server: %s", err);
       goto out;
     }

 #if DEBUG_STDERR
     fprintf (stderr,
-             "%s: data connection for %s: SSH remote port %d, local port
%s:%d\n",
+             "%s: data connection for %s: SSH remote port %d, local port
%d\n",
              g_get_prgname (), device,
              data_conns[i].nbd_remote_port,
-             nbd_local_ipaddr, nbd_local_port);
+             nbd_local_port);
 #endif
   }

   /* Create a remote directory name which will be used for libvirt
    * XML, log files and other stuff.  We don't delete this directory
    * after the run because (a) it's useful for debugging and (b) it
    * only contains small files.
    *
    * NB: This path MUST NOT require shell quoting.
    */
   time (&now);
   gmtime_r (&now, &tm);
   if (asprintf (&remote_dir,
                 "/tmp/virt-p2v-%04d%02d%02d-XXXXXXXX",
                 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday) == -1) {
     perror ("asprintf");
     cleanup_data_conns (data_conns, nr_disks);
     exit (EXIT_FAILURE);
   }
   len = strlen (remote_dir);
   guestfs_int_random_string (&remote_dir[len-8], 8);
   if (notify_ui)
     notify_ui (NOTIFY_LOG_DIR, remote_dir);

   /* Generate the local temporary directory. */
   if (mkdtemp (tmpdir) == NULL) {
     perror ("mkdtemp");
     cleanup_data_conns (data_conns, nr_disks);
     exit (EXIT_FAILURE);
   }
   memcpy (name_file, tmpdir, strlen (tmpdir));
   memcpy (physical_xml_file, tmpdir, strlen (tmpdir));
   memcpy (wrapper_script, tmpdir, strlen (tmpdir));
   memcpy (dmesg_file, tmpdir, strlen (tmpdir));
   memcpy (lscpu_file, tmpdir, strlen (tmpdir));
   memcpy (lspci_file, tmpdir, strlen (tmpdir));
   memcpy (lsscsi_file, tmpdir, strlen (tmpdir));
   memcpy (lsusb_file, tmpdir, strlen (tmpdir));
   memcpy (p2v_version_file, tmpdir, strlen (tmpdir));

   /* Generate the static files. */
   generate_name (config, name_file);
   generate_physical_xml (config, data_conns, physical_xml_file);
   generate_wrapper_script (config, remote_dir, wrapper_script);
   generate_system_data (dmesg_file,
                         lscpu_file, lspci_file, lsscsi_file, lsusb_file);
   generate_p2v_version_file (p2v_version_file);

   /* Open the control connection.  This also creates remote_dir. */
   if (notify_ui)
     notify_ui (NOTIFY_STATUS, _("Setting up the control connection
..."));

   set_control_h (start_remote_connection (config, remote_dir));
   if (control_h == NULL) {
     set_conversion_error ("could not open control connection over SSH to
the conversion server: %s",
                           get_ssh_error ());
     goto out;
   }

   /* Copy the static files to the remote dir. */

   /* These three files must not fail, so check for errors here. */
   if (scp_file (config, remote_dir,
                 name_file, physical_xml_file, wrapper_script, NULL) == -1) {
     set_conversion_error ("scp: %s: %s",
                           remote_dir, get_ssh_error ());
     goto out;
   }

   /* It's not essential that these files are copied, so ignore errors. */
   ignore_value (scp_file (config, remote_dir,
                           dmesg_file, lscpu_file, lspci_file, lsscsi_file,
                           lsusb_file, p2v_version_file, NULL));

   /* Do the conversion.  This runs until virt-v2v exits. */
   if (notify_ui)
     notify_ui (NOTIFY_STATUS, _("Doing conversion ..."));

   if (mexp_printf (control_h,
                    /* To simplify things in the wrapper script, it
                     * writes virt-v2v's exit status to
                     * /remote_dir/status, and here we read that and
                     * exit the ssh shell with the same status.
                     */
                    "%s/virt-v2v-wrapper.sh; "
                    "exit $(< %s/status)\n",
                    remote_dir, remote_dir) == -1) {
     set_conversion_error ("mexp_printf: virt-v2v: %m");
     goto out;
   }

   /* Read output from the virt-v2v process and echo it through the
    * notify function, until virt-v2v closes the connection.
    */
   while (!is_cancel_requested ()) {
     char buf[257];
     ssize_t r;

     r = read (mexp_get_fd (control_h), buf, sizeof buf - 1);
     if (r == -1) {
       /* See comment about this in miniexpect.c. */
       if (errno == EIO)
         break;                  /* EOF */
       set_conversion_error ("read: %m");
       goto out;
     }
     if (r == 0)
       break;                    /* EOF */
     buf[r] = '\0';
     if (notify_ui)
       notify_ui (NOTIFY_REMOTE_MESSAGE, buf);
   }

   if (is_cancel_requested ()) {
     set_conversion_error ("cancelled by user");
     if (notify_ui)
       notify_ui (NOTIFY_STATUS, _("Conversion cancelled by user."));
     goto out;
   }

   if (notify_ui)
     notify_ui (NOTIFY_STATUS, _("Control connection closed by
remote."));

   ret = 0;
  out:
   if (control_h) {
     mexp_h *h = control_h;
     set_control_h (NULL);
     status = mexp_close (h);

     if (status == -1) {
       set_conversion_error ("mexp_close: %m");
       ret = -1;
     }
     else if (ret == 0 &&
              WIFEXITED (status) &&
              WEXITSTATUS (status) != 0) {
       set_conversion_error ("virt-v2v exited with status %d",
                             WEXITSTATUS (status));
       ret = -1;
     }
   }
   cleanup_data_conns (data_conns, nr_disks);

   if (inhibit_fd >= 0)
     close (inhibit_fd);

   set_running (0);

   return ret;
 }
diff --git a/nbd.c b/nbd.c
index cda962d03bdd..cb849e75dc84 100644
--- a/nbd.c
+++ b/nbd.c
@@ -81,9 +81,9 @@ static const enum nbd_server standard_servers[]  static enum
nbd_server use_server;

 static pid_t start_nbdkit (const char *device, int *fds, size_t nr_fds);
-static int open_listening_socket (const char *ipaddr, int **fds, size_t
*nr_fds);
-static int bind_tcpip_socket (const char *ipaddr, const char *port, int **fds,
size_t *nr_fds);
-static int connect_with_source_port (const char *hostname, int dest_port, int
source_port);
+static int open_listening_socket (int **fds, size_t *nr_fds);
+static int bind_tcpip_socket (const char *port, int **fds, size_t *nr_fds);
+static int connect_with_source_port (int dest_port, int source_port);
 static int bind_source_port (int sockfd, int family, int source_port);

 static char *nbd_error;
@@ -235,32 +235,31 @@ test_nbd_servers (void)
  * Returns the process ID (E<gt> 0) or C<0> if there is an error.
  */
 pid_t
-start_nbd_server (const char **ipaddr, int *port, const char *device)
+start_nbd_server (int *port, const char *device)
 {
   int *fds = NULL;
   size_t i, nr_fds;
   pid_t pid;

   switch (use_server) {
   case NBDKIT:                  /* nbdkit with socket activation */
-    *ipaddr = "localhost";
-    *port = open_listening_socket (*ipaddr, &fds, &nr_fds);
+    *port = open_listening_socket (&fds, &nr_fds);
     if (*port == -1) return -1;
     pid = start_nbdkit (device, fds, nr_fds);
     for (i = 0; i < nr_fds; ++i)
       close (fds[i]);
     free (fds);
     return pid;
   }

   abort ();
 }

 #define FIRST_SOCKET_ACTIVATION_FD 3

 /**
  * Set up file descriptors and environment variables for
  * socket activation.
  *
  * Note this function runs in the child between fork and exec.
  */
@@ -350,261 +349,257 @@ start_nbdkit (const char *device, int *fds, size_t
nr_fds)
  * The caller must free the array.
  */
 static int
-open_listening_socket (const char *ipaddr, int **fds, size_t *nr_fds)
+open_listening_socket (int **fds, size_t *nr_fds)
 {
   int port;
   char port_str[16];

   /* This just ensures we don't try the port we previously bound to. */
   port = nbd_local_port;

   /* Search for a free port. */
   for (; port < 60000; ++port) {
     snprintf (port_str, sizeof port_str, "%d", port);
-    if (bind_tcpip_socket (ipaddr, port_str, fds, nr_fds) == 0) {
+    if (bind_tcpip_socket (port_str, fds, nr_fds) == 0) {
       /* See above. */
       nbd_local_port = port + 1;
       return port;
     }
   }

   set_nbd_error ("cannot find a free local port");
   return -1;
 }

 static int
-bind_tcpip_socket (const char *ipaddr, const char *port,
-                   int **fds_rtn, size_t *nr_fds_rtn)
+bind_tcpip_socket (const char *port, int **fds_rtn, size_t *nr_fds_rtn)
 {
   struct addrinfo *ai = NULL;
   struct addrinfo hints;
   struct addrinfo *a;
   int err;
   int *fds = NULL;
   size_t nr_fds;
   int addr_in_use = 0;

   memset (&hints, 0, sizeof hints);
   hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
   hints.ai_socktype = SOCK_STREAM;

-  err = getaddrinfo (ipaddr, port, &hints, &ai);
+  err = getaddrinfo ("localhost", port, &hints, &ai);
   if (err != 0) {
 #if DEBUG_STDERR
-    fprintf (stderr, "%s: getaddrinfo: %s: %s: %s",
-             g_get_prgname (), ipaddr ? ipaddr : "<any>", port,
-             gai_strerror (err));
+    fprintf (stderr, "%s: getaddrinfo: localhost: %s: %s",
g_get_prgname (),
+             port, gai_strerror (err));
 #endif
     return -1;
   }

   nr_fds = 0;

   for (a = ai; a != NULL; a = a->ai_next) {
     int sock, opt;

     sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
     if (sock == -1)
       error (EXIT_FAILURE, errno, "socket");

     opt = 1;
     if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) ==
-1)
       perror ("setsockopt: SO_REUSEADDR");

 #ifdef IPV6_V6ONLY
     if (a->ai_family == PF_INET6) {
       if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof opt) ==
-1)
         perror ("setsockopt: IPv6 only");
     }
 #endif

     if (bind (sock, a->ai_addr, a->ai_addrlen) == -1) {
       if (errno == EADDRINUSE) {
         addr_in_use = 1;
         close (sock);
         continue;
       }
       perror ("bind");
       close (sock);
       continue;
     }

     if (listen (sock, SOMAXCONN) == -1) {
       perror ("listen");
       close (sock);
       continue;
     }

     nr_fds++;
     fds = realloc (fds, sizeof (int) * nr_fds);
     if (!fds)
       error (EXIT_FAILURE, errno, "realloc");
     fds[nr_fds-1] = sock;
   }

   freeaddrinfo (ai);

   if (nr_fds == 0 && addr_in_use) {
 #if DEBUG_STDERR
-    fprintf (stderr, "%s: unable to bind to %s:%s: %s\n",
-             g_get_prgname (), ipaddr ? ipaddr : "<any>", port,
-             strerror (EADDRINUSE));
+    fprintf (stderr, "%s: unable to bind to localhost:%s: %s\n",
+             g_get_prgname (), port, strerror (EADDRINUSE));
 #endif
     return -1;
   }

 #if DEBUG_STDERR
-  fprintf (stderr, "%s: bound to IP address %s:%s (%zu socket(s))\n",
-           g_get_prgname (), ipaddr ? ipaddr : "<any>", port,
nr_fds);
+  fprintf (stderr, "%s: bound to localhost:%s (%zu socket(s))\n",
+           g_get_prgname (), port, nr_fds);
 #endif

   *fds_rtn = fds;
   *nr_fds_rtn = nr_fds;
   return 0;
 }

 /**
  * Wait for a local NBD server to start and be listening for
  * connections.
  */
 int
-wait_for_nbd_server_to_start (const char *ipaddr, int port)
+wait_for_nbd_server_to_start (int port)
 {
   int sockfd = -1;
   int result = -1;
   time_t start_t, now_t;
   struct timespec half_sec = { .tv_sec = 0, .tv_nsec = 500000000 };
   struct timeval timeout = { .tv_usec = 0 };
   char magic[8]; /* NBDMAGIC */
   size_t bytes_read = 0;
   ssize_t recvd;

   time (&start_t);

   for (;;) {
     time (&now_t);

     if (now_t - start_t >= WAIT_NBD_TIMEOUT) {
       set_nbd_error ("timed out waiting for NBD server to start");
       goto cleanup;
     }

     /* Source port for probing NBD server should be one greater than
      * port.  It's not guaranteed to always bind to this port, but it
      * will hint the kernel to start there and try incrementally
      * higher ports if needed.  This avoids the case where the kernel
      * selects port as our source port, and we immediately connect to
      * ourself.  See:
      * https://bugzilla.redhat.com/show_bug.cgi?id=1167774#c9
      */
-    sockfd = connect_with_source_port (ipaddr, port, port+1);
+    sockfd = connect_with_source_port (port, port+1);
     if (sockfd >= 0)
       break;

     nanosleep (&half_sec, NULL);
   }

   time (&now_t);
   timeout.tv_sec = (start_t + WAIT_NBD_TIMEOUT) - now_t;
   if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof
timeout) == -1) {
     set_nbd_error ("waiting for NBD server to start: "
                    "setsockopt(SO_RCVTIMEO): %m");
     goto cleanup;
   }

   do {
     recvd = recv (sockfd, magic, sizeof magic - bytes_read, 0);

     if (recvd == -1) {
       set_nbd_error ("waiting for NBD server to start: recv: %m");
       goto cleanup;
     }

     bytes_read += recvd;
   } while (bytes_read < sizeof magic);

   if (memcmp (magic, "NBDMAGIC", sizeof magic) != 0) {
     set_nbd_error ("waiting for NBD server to start: "
                    "'NBDMAGIC' was not received from NBD
server");
     goto cleanup;
   }

   result = 0;
  cleanup:
   if (sockfd >= 0)
     close (sockfd);

   return result;
 }

 /**
- * Connect to C<hostname:dest_port>, resolving the address using
+ * Connect to C<localhost:dest_port>, resolving the address using
  * L<getaddrinfo(3)>.
  *
  * This also sets the source port of the connection to the first free
  * port number E<ge> C<source_port>.
  *
  * This may involve multiple connections - to IPv4 and IPv6 for
  * instance.
  */
 static int
-connect_with_source_port (const char *hostname, int dest_port, int source_port)
+connect_with_source_port (int dest_port, int source_port)
 {
   struct addrinfo hints;
   struct addrinfo *results, *rp;
   char dest_port_str[16];
   int r, sockfd = -1;
   int reuseaddr = 1;

   snprintf (dest_port_str, sizeof dest_port_str, "%d", dest_port);

   memset (&hints, 0, sizeof hints);
   hints.ai_family = AF_UNSPEC;     /* allow IPv4 or IPv6 */
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_NUMERICSERV; /* numeric dest port number */
   hints.ai_protocol = 0;           /* any protocol */

-  r = getaddrinfo (hostname, dest_port_str, &hints, &results);
+  r = getaddrinfo ("localhost", dest_port_str, &hints,
&results);
   if (r != 0) {
-    set_nbd_error ("getaddrinfo: %s/%s: %s",
-                   hostname, dest_port_str, gai_strerror (r));
+    set_nbd_error ("getaddrinfo: localhost/%s: %s",
+                   dest_port_str, gai_strerror (r));
     return -1;
   }

   for (rp = results; rp != NULL; rp = rp->ai_next) {
     sockfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
     if (sockfd == -1)
       continue;

     /* If we run p2v repeatedly (say, running the tests in a loop),
      * there's a decent chance we'll end up trying to bind() to a port
      * that is in TIME_WAIT from a prior run.  Handle that gracefully
      * with SO_REUSEADDR.
      */
     if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR,
                     &reuseaddr, sizeof reuseaddr) == -1)
       perror ("warning: setsockopt");

     /* Need to bind the source port. */
     if (bind_source_port (sockfd, rp->ai_family, source_port) == -1) {
       close (sockfd);
       sockfd = -1;
       continue;
     }

     /* Connect. */
     if (connect (sockfd, rp->ai_addr, rp->ai_addrlen) == -1) {
       set_nbd_error ("waiting for NBD server to start: "
-                     "connect to %s/%s: %m",
-                     hostname, dest_port_str);
+                     "connect to localhost/%s: %m", dest_port_str);
       close (sockfd);
       sockfd = -1;
       continue;
     }

     break;
   }

   freeaddrinfo (results);
   return sockfd;
 }
diff --git a/p2v.h b/p2v.h
index a14edc52ef74..4d2d20648467 100644
--- a/p2v.h
+++ b/p2v.h
@@ -105,7 +105,7 @@ extern int inhibit_power_saving (void);

 /* ssh.c */
 extern int test_connection (struct config *);
-extern mexp_h *open_data_connection (struct config *, const char *local_ipaddr,
int local_port, int *remote_port);
+extern mexp_h *open_data_connection (struct config *, int local_port, int
*remote_port);
 extern mexp_h *start_remote_connection (struct config *, const char
*remote_dir);
 extern const char *get_ssh_error (void);
 extern int scp_file (struct config *config, const char *target, const char
*local, ...) __attribute__((sentinel));
@@ -113,8 +113,8 @@ extern int scp_file (struct config *config, const char
*target, const char *loca
 /* nbd.c */
 extern void set_nbd_option (const char *opt);
 extern void test_nbd_servers (void);
-extern pid_t start_nbd_server (const char **ipaddr, int *port, const char
*device);
-extern int wait_for_nbd_server_to_start (const char *ipaddr, int port);
+extern pid_t start_nbd_server (int *port, const char *device);
+extern int wait_for_nbd_server_to_start (int port);
 const char *get_nbd_error (void);

 /* utils.c */
diff --git a/ssh.c b/ssh.c
index 42701433f0cc..5e1d60b8a2e7 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1062,69 +1062,67 @@ compatible_version (const char *v2v_version)
 }

 mexp_h *
-open_data_connection (struct config *config,
-                      const char *local_ipaddr, int local_port,
-                      int *remote_port)
+open_data_connection (struct config *config, int local_port, int *remote_port)
 {
   mexp_h *h;
   char remote_arg[32];
   const char *extra_args[] = {
     "-R", remote_arg,
     "-N",
     NULL
   };
   CLEANUP_FREE char *port_str = NULL;
   const int ovecsize = 12;
   int ovector[ovecsize];

-  snprintf (remote_arg, sizeof remote_arg, "0:%s:%d", local_ipaddr,
local_port);
+  snprintf (remote_arg, sizeof remote_arg, "0:localhost:%d",
local_port);

   h = start_ssh (0, config, (char **) extra_args, 0);
   if (h == NULL)
     return NULL;

   switch (mexp_expect (h,
                        (mexp_regexp[]) {
                          { 100, .re = portfwd_re },
                          { 0 }
                        }, ovector, ovecsize)) {
   case 100:                     /* Ephemeral port. */
     port_str = strndup (&h->buffer[ovector[2]], ovector[3]-ovector[2]);
     if (port_str == NULL) {
       set_ssh_internal_error ("strndup: %m");
       mexp_close (h);
       return NULL;
     }
     if (sscanf (port_str, "%d", remote_port) != 1) {
       set_ssh_internal_error ("cannot extract the port number from
'%s'",
                               port_str);
       mexp_close (h);
       return NULL;
     }
     break;

   case MEXP_EOF:
     set_ssh_unexpected_eof ("\"ssh -R\" output");
     mexp_close (h);
     return NULL;

   case MEXP_TIMEOUT:
     set_ssh_unexpected_timeout ("\"ssh -R\" output");
     mexp_close (h);
     return NULL;

   case MEXP_ERROR:
     set_ssh_mexp_error ("mexp_expect");
     mexp_close (h);
     return NULL;

   case MEXP_PCRE_ERROR:
     set_ssh_pcre_error ();
     mexp_close (h);
     return NULL;
   }

   return h;
 }

 /* Wait for the prompt. */
--
2.19.1.3.g30247aa5d201