[Libguestfs] [nbdkit PATCH 2/2] nbd: Support TCP socket

Eric Blake eblake at redhat.com
Mon Apr 29 21:28:49 UTC 2019


I've documented a desire to do this for a while, time to actually
follow through and support connecting as a client to a TCP server.

Note that it is desirable to support the plugin connecting to an
encrypted server over TCP, then exposing the raw data over a local
Unix socket; that aspect requires yet more work, left for another
day. But even allowing an old-style client to connect to an
unencrypted TCP server is still useful.

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 plugins/nbd/nbdkit-nbd-plugin.pod |  36 ++++++++--
 plugins/nbd/nbd.c                 | 113 ++++++++++++++++++++++++++----
 TODO                              |   3 -
 3 files changed, 127 insertions(+), 25 deletions(-)

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 5add56f..21f0dcf 100644
--- a/plugins/nbd/nbdkit-nbd-plugin.pod
+++ b/plugins/nbd/nbdkit-nbd-plugin.pod
@@ -4,7 +4,7 @@ nbdkit-nbd-plugin - nbdkit nbd plugin

 =head1 SYNOPSIS

- nbdkit nbd socket=SOCKNAME [export=NAME]
+ nbdkit nbd { socket=SOCKNAME | hostname=HOST [port=PORT] } [export=NAME]

 =head1 DESCRIPTION

@@ -19,9 +19,10 @@ original server lacks it).  Use of this plugin along with nbdkit
 filters (adding I<--filter> to the nbdkit command line) makes it
 possible to apply any nbdkit filter to any other NBD server.

-For now, this is limited to connecting to another NBD server over a
-named Unix socket without TLS, although it is feasible that future
-additions will support network sockets and encryption.
+For now, this is limited to connecting to another NBD server over an
+unencrypted connection; if the data is sensitive, it is better to
+stick to a Unix socket rather than transmitting plaintext over TCP. It
+is feasible that future additions will support encryption.

 =head1 PARAMETERS

@@ -29,10 +30,19 @@ additions will support network sockets and encryption.

 =item B<socket=>SOCKNAME

-Connect to the NBD server located at the Unix socket C<SOCKNAME>.  The
-server can speak either new or old style protocol.
+Connect to the NBD server located at the Unix socket C<SOCKNAME>.

-This parameter is required.
+=item B<hostname=>HOST
+
+Connect to the NBD server at the given remote C<HOST> using a TCP socket.
+
+=item B<port=>PORT
+
+When B<hostname> is supplied, use B<PORT> instead of the default port
+10809.
+
+Either B<socket> or B<hostname> must be provided. The server can speak
+either new or old style protocol.

 =item B<export=>NAME

@@ -66,6 +76,18 @@ the same task as the deprecated C<qemu-nbd -P 1 -f qcow2
    nbdkit --exit-with-parent --filter=partition nbd socket=$sock partition=1 &
    exec qemu-nbd -k $sock -f qcow2 /path/to/image.qcow2 )

+Conversely, expose the contents of export I<foo> from a new style
+server with unencrypted data to a client that can only consume
+unencrypted old style. Use I<--run> to clean up nbdkit at the time the
+client exits.
+
+ nbdkit -U - -o nbd hostname=example.com export=foo \
+  --run '/path/to/oldclient --socket=$unixsocket'
+
+ ┌────────────┐          ┌────────┐          ┌────────────┐
+ │ old client │ ────────▶│ nbdkit │ ────────▶│ new server │
+ └────────────┘   Unix   └────────┘   TCP    └────────────┘
+
 =head1 SEE ALSO

 L<nbdkit(1)>,
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index a826363..ce6859d 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -35,11 +35,15 @@
 #include <stdlib.h>
 #include <stddef.h>
 #include <stdbool.h>
+#include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <assert.h>
@@ -55,6 +59,13 @@
 /* Connect to server via absolute name of Unix socket */
 static char *sockname;

+/* Connect to server via TCP socket */
+static const char *hostname;
+static const char *port;
+
+/* Human-readable server description */
+static char *servname;
+
 /* Name of export on remote server, default '', ignored for oldstyle */
 static const char *export;

@@ -62,10 +73,12 @@ static void
 nbd_unload (void)
 {
   free (sockname);
+  free (servname);
 }

 /* Called for each key=value passed on the command line.  This plugin
- * accepts socket=<sockname> (required for now) and export=<name> (optional).
+ * accepts socket=<sockname> or hostname=<hostname>/port=<port>
+ * (exactly one connection required) and export=<name> (optional).
  */
 static int
 nbd_config (const char *key, const char *value)
@@ -77,6 +90,10 @@ nbd_config (const char *key, const char *value)
     if (!sockname)
       return -1;
   }
+  else if (strcmp (key, "hostname") == 0)
+    hostname = value;
+  else if (strcmp (key, "port") == 0)
+    port = value;
   else if (strcmp (key, "export") == 0)
     export = value;
   else {
@@ -87,29 +104,52 @@ nbd_config (const char *key, const char *value)
   return 0;
 }

-/* Check the user did pass a socket=<SOCKNAME> parameter. */
+/* Check the user passed exactly one socket description. */
 static int
 nbd_config_complete (void)
 {
-  struct sockaddr_un sock;
+  int r;

-  if (sockname == NULL) {
-    nbdkit_error ("you must supply the socket=<SOCKNAME> parameter "
-                  "after the plugin name on the command line");
-    return -1;
+  if (sockname) {
+    struct sockaddr_un sock;
+
+    if (hostname || port) {
+      nbdkit_error ("cannot mix Unix socket and TCP hostname/port parameters");
+      return -1;
+    }
+    if (strlen (sockname) > sizeof sock.sun_path) {
+      nbdkit_error ("socket file name too large");
+      return -1;
+    }
+    servname = strdup (sockname);
   }
-  if (strlen (sockname) > sizeof sock.sun_path) {
-    nbdkit_error ("socket file name too large");
-    return -1;
+  else {
+    if (!hostname) {
+      nbdkit_error ("must supply socket= or hostname= of external NBD server");
+      return -1;
+    }
+    if (!port)
+      port = "10809";
+    if (strchr (hostname, ':'))
+      r = asprintf (&servname, "[%s]:%s", hostname, port);
+    else
+      r = asprintf (&servname, "%s:%s", hostname, port);
+    if (r < 0) {
+      nbdkit_error ("asprintf: %m");
+      return -1;
+    }
   }
+
   if (!export)
     export = "";
   return 0;
 }

 #define nbd_config_help \
-  "socket=<SOCKNAME>   (required) The Unix socket to connect to.\n" \
-  "export=<NAME>                  Export name to connect to (default \"\").\n" \
+  "socket=<SOCKNAME>      The Unix socket to connect to.\n" \
+  "hostname=<HOST>        The hostname for the TCP socket to connect to.\n" \
+  "port=<PORT>            TCP port or service name to use (default 10809).\n" \
+  "export=<NAME>          Export name to connect to (default \"\").\n" \

 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL

@@ -199,7 +239,7 @@ nbd_mark_dead (struct handle *h)
   ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&h->trans_lock);
   if (!h->dead) {
     nbdkit_debug ("permanent failure while talking to server %s: %m",
-                  sockname);
+                  servname);
     h->dead = true;
   }
   else if (!err)
@@ -861,6 +901,45 @@ nbd_connect_unix(struct handle *h)
   return 0;
 }

+/* Connect to a TCP socket */
+static int
+nbd_connect_tcp(struct handle *h)
+{
+  struct addrinfo hints = { .ai_family = AF_UNSPEC,
+                            .ai_socktype = SOCK_STREAM, };
+  struct addrinfo *result, *rp;
+  int r;
+  const int optval = 1;
+
+  nbdkit_debug ("connecting to TCP socket host=%s port=%s", hostname, port);
+  r = getaddrinfo (hostname, port, &hints, &result);
+  if (r != 0) {
+    nbdkit_error ("getaddrinfo: %s", gai_strerror(r));
+    return -1;
+  }
+
+  for (rp = result; rp; rp = rp->ai_next) {
+    h->fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    if (h->fd == -1)
+      continue;
+    if (connect (h->fd, rp->ai_addr, rp->ai_addrlen) != -1)
+      break;
+    close (h->fd);
+  }
+  freeaddrinfo (result);
+  if (rp == NULL) {
+    nbdkit_error ("connect: %m");
+    return -1;
+  }
+
+  if (setsockopt (h->fd, IPPROTO_TCP, TCP_NODELAY, &optval,
+                  sizeof(int)) == -1) {
+    nbdkit_error ("cannot set TCP_NODELAY option: %m");
+    return -1;
+  }
+  return 0;
+}
+
 /* Create the per-connection handle. */
 static void *
 nbd_open (int readonly)
@@ -876,7 +955,11 @@ nbd_open (int readonly)
   }
   h->fd = -1;

-  if (nbd_connect_unix (h) == -1)
+  if (sockname) {
+    if (nbd_connect_unix (h) == -1)
+      goto err;
+  }
+  else if (nbd_connect_tcp (h) == -1)
     goto err;

   /* old and new handshake share same meaning of first 16 bytes */
@@ -885,7 +968,7 @@ nbd_open (int readonly)
     goto err;
   }
   if (strncmp(old.nbdmagic, "NBDMAGIC", sizeof old.nbdmagic)) {
-    nbdkit_error ("wrong magic, %s is not an NBD server", sockname);
+    nbdkit_error ("wrong magic, %s is not an NBD server", servname);
     goto err;
   }
   version = be64toh (old.version);
diff --git a/TODO b/TODO
index 53d3358..b9ddb1e 100644
--- a/TODO
+++ b/TODO
@@ -97,9 +97,6 @@ nbdkit-nbd-plugin could use enhancements:
   would need TLS to support a plain client connecting to an encrypted
   server).

-* Support for connecting to a server over IP rather than just Unix
-  sockets.
-
 nbdkit-floppy-plugin:

 * Add boot sector support.  In theory this is easy (eg. using
-- 
2.20.1




More information about the Libguestfs mailing list