Either wallclock time, uptime or time since client connection can be
reflected back to the client in a big endian binary structure.
$ nbdkit reflection time --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
00000000 00 00 00 00 5d 8f 24 c7 00 04 24 01
\ | / /
$ date --date="@$(( 0x5d8f24c7 ))"
Sat 28 Sep 10:15:51 BST 2019
$ nbdkit reflection uptime --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
00000000 00 00 00 00 00 00 00 00 00 00 60 4b
| |
0x604b is about 25ms
$ nbdkit reflection conntime --run 'nbdsh --connect $uri -c
"sys.stdout.buffer.write(h.pread(12,0))" | hexdump -C'
00000000 00 00 00 00 00 00 00 00 00 00 00 e0
|
0xe0 is about 200μs
Suggested by Eric Blake.
---
plugins/reflection/Makefile.am | 1 +
.../reflection/nbdkit-reflection-plugin.pod | 41 ++++++++-
plugins/reflection/reflection.c | 90 ++++++++++++++++++-
3 files changed, 128 insertions(+), 4 deletions(-)
diff --git a/plugins/reflection/Makefile.am b/plugins/reflection/Makefile.am
index 40aa786..9544d98 100644
--- a/plugins/reflection/Makefile.am
+++ b/plugins/reflection/Makefile.am
@@ -42,6 +42,7 @@ nbdkit_reflection_plugin_la_SOURCES = \
nbdkit_reflection_plugin_la_CPPFLAGS = \
-I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include \
$(NULL)
nbdkit_reflection_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
nbdkit_reflection_plugin_la_LDFLAGS = \
diff --git a/plugins/reflection/nbdkit-reflection-plugin.pod
b/plugins/reflection/nbdkit-reflection-plugin.pod
index f971cef..09deb26 100644
--- a/plugins/reflection/nbdkit-reflection-plugin.pod
+++ b/plugins/reflection/nbdkit-reflection-plugin.pod
@@ -4,7 +4,8 @@ nbdkit-reflection-plugin - reflect client info back to the client
=head1 SYNOPSIS
- nbdkit reflection [mode=]exportname|base64exportname|address
+ nbdkit reflection [mode=]exportname|base64exportname|address|
+ time|uptime|conntime
=head1 DESCRIPTION
@@ -22,6 +23,10 @@ than this.
C<mode=address> creates a disk which contains the client's IP address
and port number as a string.
+C<mode=time>, C<mode=uptime> and C<mode=conntime> report server
+wallclock time, nbdkit uptime, or time since the connection was opened
+respectively and may be used to measure latency.
+
The plugin only supports read-only access. To make the disk writable,
add L<nbdkit-cow-filter(1)> on top.
@@ -103,6 +108,40 @@ name cannot contain ASCII NUL characters.
This is the default mode.
+=item [B<mode=>]B<time>
+
+Reflect server wallclock time as seconds and microseconds since the
+Epoch (see L<gettimeofday(2)>):
+
+ ┌────────┬────────┬────────────┬──────────────────────┐
+ │ offset │ length │ format │ field │
+ ╞════════╪════════╪════════════╪══════════════════════╡
+ │ 0 │ 8 │ 64 bit int │ seconds │
+ │ │ │ big endian │ │
+ ├────────┼────────┼────────────┼──────────────────────┤
+ │ 8 │ 4 │ 32 bit int │ microseconds │
+ │ │ │ big endian │ │
+ └────────┴────────┴────────────┴──────────────────────┘
+
+To be able to read this atomically you must read the whole 12 bytes in
+a single request.
+
+Note that exposing server time may be insecure. It is safer to use
+C<mode=uptime> or C<mode=conntime> instead.
+
+=item [B<mode=>]B<uptime>
+
+Reflect nbdkit uptime in seconds and microseconds (ie. both fields are
+C<0> immediately after nbdkit starts, although a client would never be
+able to observe this). The format is exactly the same as for
+C<mode=time> above.
+
+=item [B<mode=>]B<conntime>
+
+Reflect time since the NBD client connection was opened in seconds and
+milliseconds. The format is exactly the same as for C<mode=time>
+above.
+
C<mode=> is a magic config key and may be omitted in most cases.
See L<nbdkit(1)/Magic parameters>.
diff --git a/plugins/reflection/reflection.c b/plugins/reflection/reflection.c
index 6fd1962..1459503 100644
--- a/plugins/reflection/reflection.c
+++ b/plugins/reflection/reflection.c
@@ -35,6 +35,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
@@ -48,14 +49,29 @@
#include <nbdkit-plugin.h>
+#include "byte-swapping.h"
+#include "tvdiff.h"
+
/* The mode. */
enum mode {
MODE_EXPORTNAME,
MODE_BASE64EXPORTNAME,
MODE_ADDRESS,
+ MODE_TIME,
+ MODE_UPTIME,
+ MODE_CONNTIME,
};
static enum mode mode = MODE_EXPORTNAME;
+/* Plugin load time. */
+static struct timeval load_t;
+
+static void
+reflection_load (void)
+{
+ gettimeofday (&load_t, NULL);
+}
+
static int
reflection_config (const char *key, const char *value)
{
@@ -73,9 +89,14 @@ reflection_config (const char *key, const char *value)
return -1;
#endif
}
- else if (strcasecmp (value, "address") == 0) {
+ else if (strcasecmp (value, "address") == 0)
mode = MODE_ADDRESS;
- }
+ else if (strcasecmp (value, "time") == 0)
+ mode = MODE_TIME;
+ else if (strcasecmp (value, "uptime") == 0)
+ mode = MODE_UPTIME;
+ else if (strcasecmp (value, "conntime") == 0)
+ mode = MODE_CONNTIME;
else {
nbdkit_error ("unknown mode: '%s'", value);
return -1;
@@ -90,7 +111,8 @@ reflection_config (const char *key, const char *value)
}
#define reflection_config_help \
- "mode=exportname|base64exportname|address Plugin mode (default
exportname)."
+ "mode=exportname|base64exportname|address|time|uptime\n" \
+ " Plugin mode (default exportname)."
/* Provide a way to detect if the base64 feature is supported. */
static void
@@ -105,6 +127,7 @@ reflection_dump_plugin (void)
struct handle {
void *data; /* Block device data. */
size_t len; /* Length of data in bytes. */
+ struct timeval conn_t; /* Time since connection was opened. */
};
static int
@@ -279,6 +302,19 @@ reflection_open (int readonly)
}
return h;
+ case MODE_TIME:
+ case MODE_UPTIME:
+ case MODE_CONNTIME:
+ gettimeofday (&h->conn_t, NULL);
+ h->len = 12;
+ h->data = malloc (h->len);
+ if (h->data == NULL) {
+ nbdkit_error ("malloc: %m");
+ free (h);
+ return NULL;
+ }
+ return h;
+
default:
abort ();
}
@@ -320,6 +356,13 @@ reflection_can_multi_conn (void *handle)
*/
case MODE_ADDRESS:
return 0;
+ /* All time modes will read different values at different times,
+ * so all of them are unsafe for multi-conn.
+ */
+ case MODE_TIME:
+ case MODE_UPTIME:
+ case MODE_CONNTIME:
+ return 0;
/* Keep GCC happy. */
default:
@@ -337,6 +380,42 @@ reflection_can_cache (void *handle)
return NBDKIT_CACHE_NATIVE;
}
+static void
+update_time (struct handle *h)
+{
+ struct timeval tv;
+ int64_t secs;
+ int32_t usecs;
+ char *p;
+
+ gettimeofday (&tv, NULL);
+
+ switch (mode) {
+ case MODE_TIME:
+ break;
+
+ case MODE_UPTIME:
+ subtract_timeval (&load_t, &tv, &tv);
+ break;
+
+ case MODE_CONNTIME:
+ subtract_timeval (&h->conn_t, &tv, &tv);
+ break;
+
+ default:
+ abort ();
+ }
+
+ /* Pack the result into the output buffer. */
+ secs = tv.tv_sec;
+ usecs = tv.tv_usec;
+ secs = htobe64 (secs);
+ usecs = htobe32 (usecs);
+ p = h->data;
+ memcpy (&p[0], &secs, 8);
+ memcpy (&p[8], &usecs, 4);
+}
+
/* Read data. */
static int
reflection_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
@@ -344,6 +423,10 @@ reflection_pread (void *handle, void *buf, uint32_t count, uint64_t
offset,
{
struct handle *h = handle;
+ /* For the time modes we update the data on every read. */
+ if (mode == MODE_TIME || mode == MODE_UPTIME || mode == MODE_CONNTIME)
+ update_time (h);
+
memcpy (buf, h->data + offset, count);
return 0;
}
@@ -351,6 +434,7 @@ reflection_pread (void *handle, void *buf, uint32_t count, uint64_t
offset,
static struct nbdkit_plugin plugin = {
.name = "reflection",
.version = PACKAGE_VERSION,
+ .load = reflection_load,
.config = reflection_config,
.config_help = reflection_config_help,
.dump_plugin = reflection_dump_plugin,
--
2.23.0