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(a)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