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 <[email protected]> --- 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 _______________________________________________ Libguestfs mailing list [email protected] https://www.redhat.com/mailman/listinfo/libguestfs
