[Libguestfs] [PATCH nbdkit 2/2] reflection: Add mode for reflecting server time.

Richard W.M. Jones
Sat, 28 Sep 2019 09:25:17 -0000

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
+
+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