---
.../reflection/nbdkit-reflection-plugin.pod | 23 ++++-
plugins/reflection/reflection.c | 88 +++++++++++++++++++
tests/Makefile.am | 2 +
tests/test-reflection-address.sh | 63 +++++++++++++
4 files changed, 174 insertions(+), 2 deletions(-)
diff --git a/plugins/reflection/nbdkit-reflection-plugin.pod
b/plugins/reflection/nbdkit-reflection-plugin.pod
index 1b260b6..7f52c58 100644
--- a/plugins/reflection/nbdkit-reflection-plugin.pod
+++ b/plugins/reflection/nbdkit-reflection-plugin.pod
@@ -1,10 +1,10 @@
=head1 NAME
-nbdkit-reflection-plugin - reflect export name back to the client
+nbdkit-reflection-plugin - reflect client info back to the client
=head1 SYNOPSIS
- nbdkit reflection [mode=]exportname|base64exportname
+ nbdkit reflection [mode=]exportname|base64exportname|address
=head1 DESCRIPTION
@@ -17,6 +17,9 @@ similar except the client must base64-encode the data in the export
name, allowing arbitrary binary data to be sent (see L</EXAMPLES>
below to make this clearer).
+C<mode=address> creates a disk which contains the client's IP address
+and port number as a string.
+
The plugin only supports read-only access. To make the disk writable,
add L<nbdkit-cow-filter(1)> on top.
@@ -57,10 +60,26 @@ qemu command line:
AAAAAAAAAAAAAAAAAAAAVao=
'
+Another use for the reflection plugin is to send back the client's IP
+address:
+
+ $ nbdkit reflection mode=address
+ $ nbdsh -u 'nbd://localhost' -c 'print(h.pread(h.get_size(), 0))'
+
+which will print something like:
+
+ b'[::1]:58912'
+
=head1 PARAMETERS
=over 4
+=item [B<mode=>]B<address>
+
+Reflect the client's IP address and client port number as a string in
+the usual format. For Unix sockets this sets the disk to the string
+C<"unix"> to avoid leaking host paths.
+
=item [B<mode=>]B<base64exportname>
Reflect the export name passed by the client, assuming the client
diff --git a/plugins/reflection/reflection.c b/plugins/reflection/reflection.c
index 74f7169..a0d7c60 100644
--- a/plugins/reflection/reflection.c
+++ b/plugins/reflection/reflection.c
@@ -35,6 +35,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
#if defined(HAVE_GNUTLS) && defined(HAVE_GNUTLS_BASE64_DECODE2)
#include <gnutls/gnutls.h>
@@ -49,6 +52,7 @@
enum mode {
MODE_EXPORTNAME,
MODE_BASE64EXPORTNAME,
+ MODE_ADDRESS,
};
static enum mode mode = MODE_EXPORTNAME;
@@ -67,6 +71,9 @@ reflection_config (const char *key, const char *value)
return -1;
#endif
}
+ else if (strcasecmp (value, "address") == 0) {
+ mode = MODE_ADDRESS;
+ }
else {
nbdkit_error ("unknown mode: '%s'", value);
return -1;
@@ -137,6 +144,74 @@ decode_base64 (const char *data, size_t len, struct handle *ret)
#endif
}
+static int
+handle_address (struct sockaddr *sa, socklen_t addrlen,
+ struct handle *ret)
+{
+ struct sockaddr_in *addr = (struct sockaddr_in *) sa;
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) sa;
+ union {
+ char straddr[INET_ADDRSTRLEN];
+ char straddr6[INET6_ADDRSTRLEN];
+ } u;
+ int r;
+ char *str;
+
+ switch (addr->sin_family) {
+ case AF_INET:
+ if (inet_ntop (AF_INET, &addr->sin_addr,
+ u.straddr, sizeof u.straddr) == NULL) {
+ nbdkit_error ("inet_ntop: %m");
+ return -1;
+ }
+ r = asprintf (&str, "%s:%d", u.straddr, ntohs (addr->sin_port));
+ if (r == -1) {
+ nbdkit_error ("asprintf: %m");
+ return -1;
+ }
+ ret->len = r;
+ ret->data = str;
+ return 0;
+
+ case AF_INET6:
+ if (inet_ntop (AF_INET6, &addr6->sin6_addr,
+ u.straddr6, sizeof u.straddr6) == NULL) {
+ nbdkit_error ("inet_ntop: %m");
+ return -1;
+ }
+ r = asprintf (&str, "[%s]:%d", u.straddr6, ntohs
(addr6->sin6_port));
+ if (r == -1) {
+ nbdkit_error ("asprintf: %m");
+ return -1;
+ }
+ ret->len = r;
+ ret->data = str;
+ return 0;
+
+ case AF_UNIX:
+ /* We don't want to expose the socket path because it's a host
+ * filesystem name. The client might not really be running on the
+ * same machine (eg. it using a proxy). However it doesn't even
+ * matter because getpeername(2) on Linux returns a zero length
+ * sun_path in this case!
+ */
+ str = strdup ("unix");
+ if (str == NULL) {
+ nbdkit_error ("strdup: %m");
+ return -1;
+ }
+ ret->len = strlen (str);
+ ret->data = str;
+ return 0;
+
+ default:
+ nbdkit_debug ("unsupported socket family %d", addr->sin_family);
+ ret->data = NULL;
+ ret->len = 0;
+ return 0;
+ }
+}
+
/* Create the per-connection handle.
*
* This is a rather unusual plugin because it has to parse data sent
@@ -147,12 +222,16 @@ decode_base64 (const char *data, size_t len, struct handle *ret)
* - Inputs that result in unbounded output.
*
* - Inputs that could hang, crash or exploit the server.
+ *
+ * - Leaking host information (eg. paths).
*/
static void *
reflection_open (int readonly)
{
const char *export_name;
size_t export_name_len;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
struct handle *h;
h = malloc (sizeof *h);
@@ -189,6 +268,15 @@ reflection_open (int readonly)
return h;
}
+ case MODE_ADDRESS:
+ addrlen = sizeof addr;
+ if (nbdkit_peer_name ((struct sockaddr *) &addr, &addrlen) == -1 ||
+ handle_address ((struct sockaddr *) &addr, addrlen, h) == -1) {
+ free (h);
+ return NULL;
+ }
+ return h;
+
default:
abort ();
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 69d5d5e..1b1e05b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -111,6 +111,7 @@ EXTRA_DIST = \
test-rate-dynamic.sh \
test.rb \
test-readahead-copy.sh \
+ test-reflection-address.sh \
test-reflection-base64.sh \
test-reflection-raw.sh \
test-shutdown.sh \
@@ -606,6 +607,7 @@ test_random_LDADD = libtest.la $(LIBGUESTFS_LIBS)
# reflection plugin test.
TESTS += \
+ test-reflection-address.sh \
test-reflection-base64.sh \
test-reflection-raw.sh \
$(NULL)
diff --git a/tests/test-reflection-address.sh b/tests/test-reflection-address.sh
new file mode 100755
index 0000000..68a4e8e
--- /dev/null
+++ b/tests/test-reflection-address.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-2019 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+# Test the relection plugin with mode=address.
+
+source ./functions.sh
+set -e
+set -x
+
+requires nbdsh --version
+
+sock=`mktemp -u`
+files="reflection-address.out reflection-address.pid $sock"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Run nbdkit.
+start_nbdkit -P reflection-address.pid -U $sock \
+ reflection mode=address
+
+export sock
+nbdsh -c - <<'EOF'
+import os
+import re
+
+h.connect_unix (os.environ["sock"])
+
+size = h.get_size ()
+assert size > 0
+
+buf = h.pread (size, 0)
+print ("buf = %r" % buf)
+assert buf == b'unix'
+EOF
--
2.23.0