mbox series

[0/5] virtio: Add vhost-user based RNG service

Message ID 20210601200305.1772391-1-mathieu.poirier@linaro.org
Headers show
Series virtio: Add vhost-user based RNG service | expand

Message

Mathieu Poirier June 1, 2021, 8:03 p.m. UTC
Hi all,

This sets adds a vhost-user based random number generator (RNG),
similar to what has been done for i2c and virtiofsd.  In fact
the implementation for vhost-user-rng and vhost-user-rng-pci
follow what was done for vhost-user-i2c.

Applies cleanly to git://git.qemu.org/qemu.git master (52848929b70d).

Regards,
Mathieu

Mathieu Poirier (5):
  vhost-user-rng: Add vhost-user-rng implementation
  vhost-user-rng-pci: Add vhost-user-rng-pci implementation
  vhost-user-rng: backend: Add RNG vhost-user daemon implementation
  docs: Add documentation for vhost based RNG implementation
  MAINTAINERS: Add maintainer for vhost-user RNG implementation

 MAINTAINERS                              |   9 +
 docs/tools/vhost-user-rng.rst            |  74 +++++
 hw/virtio/Kconfig                        |   5 +
 hw/virtio/meson.build                    |   2 +
 hw/virtio/vhost-user-rng-pci.c           |  79 +++++
 hw/virtio/vhost-user-rng.c               | 294 +++++++++++++++++
 include/hw/virtio/vhost-user-rng.h       |  33 ++
 tools/meson.build                        |   8 +
 tools/vhost-user-rng/50-qemu-rng.json.in |   5 +
 tools/vhost-user-rng/main.c              | 403 +++++++++++++++++++++++
 tools/vhost-user-rng/meson.build         |  10 +
 11 files changed, 922 insertions(+)
 create mode 100644 docs/tools/vhost-user-rng.rst
 create mode 100644 hw/virtio/vhost-user-rng-pci.c
 create mode 100644 hw/virtio/vhost-user-rng.c
 create mode 100644 include/hw/virtio/vhost-user-rng.h
 create mode 100644 tools/vhost-user-rng/50-qemu-rng.json.in
 create mode 100644 tools/vhost-user-rng/main.c
 create mode 100644 tools/vhost-user-rng/meson.build

-- 
2.25.1

Comments

Joakim Bech June 1, 2021, 9:01 p.m. UTC | #1
On Tue, Jun 01, 2021 at 08:03:20PM +0000, Mathieu Poirier via Stratos-dev wrote:
> This patch provides the vhost-user backend implementation to work

> in tandem with the vhost-user-rng implementation of the QEMU VMM.

> 

> It uses the vhost-user API so that other VMM can re-use the interface

> without having to write the driver again.

> 

> Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>

> ---

>  tools/meson.build                        |   8 +

>  tools/vhost-user-rng/50-qemu-rng.json.in |   5 +

>  tools/vhost-user-rng/main.c              | 403 +++++++++++++++++++++++

>  tools/vhost-user-rng/meson.build         |  10 +

>  4 files changed, 426 insertions(+)

>  create mode 100644 tools/vhost-user-rng/50-qemu-rng.json.in

>  create mode 100644 tools/vhost-user-rng/main.c

>  create mode 100644 tools/vhost-user-rng/meson.build

> 

> diff --git a/tools/meson.build b/tools/meson.build

> index 3e5a0abfa29f..66b0a11fbb45 100644

> --- a/tools/meson.build

> +++ b/tools/meson.build

> @@ -24,3 +24,11 @@ endif

>  if have_virtiofsd

>    subdir('virtiofsd')

>  endif

> +

> +have_virtiorng = (have_system and

> +    have_tools and

> +    'CONFIG_LINUX' in config_host)

> +

> +if have_virtiorng

> +  subdir('vhost-user-rng')

> +endif

> diff --git a/tools/vhost-user-rng/50-qemu-rng.json.in b/tools/vhost-user-rng/50-qemu-rng.json.in

> new file mode 100644

> index 000000000000..9186c3c6fe1d

> --- /dev/null

> +++ b/tools/vhost-user-rng/50-qemu-rng.json.in

> @@ -0,0 +1,5 @@

> +{

> +  "description": "QEMU vhost-user-rng",

> +  "type": "bridge",

> +  "binary": "@libexecdir@/vhost-user-rng"

> +}

> diff --git a/tools/vhost-user-rng/main.c b/tools/vhost-user-rng/main.c

> new file mode 100644

> index 000000000000..ba7380b12659

> --- /dev/null

> +++ b/tools/vhost-user-rng/main.c

> @@ -0,0 +1,403 @@

> +/*

> + * VIRTIO RNG Emulation via vhost-user

> + *

> + * Copyright (c) 2021 Mathieu Poirier <mathieu.poirier@linaro.org>

> + *

> + * SPDX-License-Identifier: GPL-2.0-or-later

> + */

> +

> +#define G_LOG_DOMAIN "vhost-user-rng"

> +#define G_LOG_USE_STRUCTURED 1

> +

> +#include <glib.h>

> +#include <gio/gio.h>

> +#include <gio/gunixsocketaddress.h>

> +#include <glib-unix.h>

> +#include <glib/gstdio.h>

> +#include <pthread.h>

> +#include <signal.h>

> +#include <stdio.h>

> +#include <stdbool.h>

> +#include <string.h>

> +#include <inttypes.h>

> +#include <fcntl.h>

> +#include <sys/ioctl.h>

> +#include <sys/types.h>

> +#include <sys/stat.h>

> +#include <sys/mman.h>

> +#include <time.h>

> +#include <unistd.h>

> +#include <endian.h>

> +#include <assert.h>

> +

> +#include "qemu/cutils.h"

> +#include "subprojects/libvhost-user/libvhost-user-glib.h"

> +#include "subprojects/libvhost-user/libvhost-user.h"

> +

> +#ifndef container_of

> +#define container_of(ptr, type, member) ({                      \

> +        const typeof(((type *) 0)->member) * __mptr = (ptr);     \

> +        (type *) ((char *) __mptr - offsetof(type, member)); })

> +#endif

> +

> +typedef struct {

> +    VugDev dev;

> +    struct itimerspec ts;

> +    timer_t rate_limit_timer;

> +    pthread_mutex_t rng_mutex;

> +    pthread_cond_t rng_cond;

> +    int64_t quota_remaining;

> +    bool activate_timer;

> +    GMainLoop *loop;

> +} VuRNG;

> +

> +static gboolean print_cap, verbose;

> +static gchar *source_path, *socket_path;

> +static gint source_fd, socket_fd = -1;

> +

> +/* Defaults tailored on virtio-rng.c */

> +static uint32_t period_ms = 1 << 16;

> +static uint64_t max_bytes = INT64_MAX;

> +

> +static void check_rate_limit(union sigval sv)

> +{

> +    VuRNG *rng = sv.sival_ptr;

> +    bool wakeup = false;

> +

> +    pthread_mutex_lock(&rng->rng_mutex);

> +    /*

> +     * The timer has expired and the guest has used all available

> +     * antropy, which means function vu_rng_handle_request() is waiting

s/antropy/entropy/

I also found 'antropy' at two more locations in this patch (that made me
wonder).

> +     * on us.  As such wake it up once we're done here.

> +     */

> +    if (rng->quota_remaining == 0) {

> +        wakeup = true;

> +    }

> +

> +    /*

> +     * Reset the antropy available to the guest and tell function

> +     * vu_rng_handle_requests() to start the timer before using it.

> +     */

> +    rng->quota_remaining = max_bytes;

> +    rng->activate_timer = true;

> +    pthread_mutex_unlock(&rng->rng_mutex);

> +

> +    if (wakeup) {

> +        pthread_cond_signal(&rng->rng_cond);

> +    }

> +}

> +

> +static void setup_timer(VuRNG *rng)

> +{

> +    struct sigevent sev;

> +    int ret;

> +

> +    memset(&rng->ts, 0, sizeof(struct itimerspec));

> +    rng->ts.it_value.tv_sec = period_ms / 1000;

> +    rng->ts.it_value.tv_nsec = (period_ms % 1000) * 1000000;

> +

> +    /*

> +     * Call function check_rate_limit() as if it was the start of

> +     * a new thread when the timer expires.

> +     */

> +    sev.sigev_notify = SIGEV_THREAD;

> +    sev.sigev_notify_function = check_rate_limit;

> +    sev.sigev_value.sival_ptr = rng;

> +    /* Needs to be NULL if defaults attributes are to be used. */

> +    sev.sigev_notify_attributes = NULL;

> +    ret = timer_create(CLOCK_MONOTONIC, &sev, &rng->rate_limit_timer);

> +    if (ret < 0) {

> +        fprintf(stderr, "timer_create() failed\n");

> +    }

> +

> +}

> +

> +

> +/* Virtio helpers */

> +static uint64_t rng_get_features(VuDev *dev)

> +{

> +    if (verbose) {

> +        g_info("%s: replying", __func__);

> +    }

> +    return 0;

> +}

> +

> +static void rng_set_features(VuDev *dev, uint64_t features)

> +{

> +    if (verbose && features) {

> +        g_autoptr(GString) s = g_string_new("Requested un-handled feature");

> +        g_string_append_printf(s, " 0x%" PRIx64 "", features);

> +        g_info("%s: %s", __func__, s->str);

> +    }

> +}

> +

> +static void vu_rng_handle_requests(VuDev *dev, int qidx)

> +{

> +    VuRNG *rng = container_of(dev, VuRNG, dev.parent);

> +    VuVirtq *vq = vu_get_queue(dev, qidx);

> +    VuVirtqElement *elem;

> +    size_t to_read;

> +    int len, ret;

> +

> +    for (;;) {

> +        /* Get element in the vhost virtqueue */

> +        elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement));

> +        if (!elem) {

> +            break;

> +        }

> +

> +        /* Get the amount of antropy to read from the vhost server */

> +        to_read = elem->in_sg[0].iov_len;

> +

> +        pthread_mutex_lock(&rng->rng_mutex);

> +

> +        /*

> +         * We have consumed all entropy available for this time slice.

> +         * Wait for the timer (check_rate_limit()) to tell us about the

> +         * start of a new time slice.

> +         */

> +        if (rng->quota_remaining == 0) {

> +            pthread_cond_wait(&rng->rng_cond, &rng->rng_mutex);

> +        }

> +

> +        /* Start the timer if the last time slice has expired */

> +        if (rng->activate_timer == true) {

> +            rng->activate_timer = false;

> +            ret = timer_settime(rng->rate_limit_timer, 0, &rng->ts, NULL);

> +            if (ret < 0) {

> +                fprintf(stderr, "timer_settime() failed\n");

> +            }

> +        }

> +

> +        /* Make sure we don't read more than it's available */

> +        if (rng->quota_remaining < to_read) {

> +            to_read = rng->quota_remaining;

> +        }

> +

> +        len = read(source_fd, elem->in_sg[0].iov_base, to_read);

> +

> +        /* Simply return 0 if an error occurs */

> +        if (len < 0) {

> +            len = 0;

> +        }

> +

> +        rng->quota_remaining -= len;

> +

> +        pthread_mutex_unlock(&rng->rng_mutex);

> +

> +        vu_queue_push(dev, vq, elem, len);

> +        free(elem);

> +    }

> +

> +    vu_queue_notify(dev, vq);

> +}

> +

> +static void

> +vu_rng_queue_set_started(VuDev *dev, int qidx, bool started)

> +{

> +    VuVirtq *vq = vu_get_queue(dev, qidx);

> +

> +    g_debug("queue started %d:%d\n", qidx, started);

> +

> +    if (!qidx) {

> +        vu_set_queue_handler(dev, vq, started ? vu_rng_handle_requests : NULL);

> +    }

> +}

> +

> +/*

> + * Any messages not handled here are processed by the libvhost library

> + * itself.

> + */

> +static int rng_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply)

> +{

> +    VuRNG *rng = container_of(dev, VuRNG, dev.parent);

> +

> +    if (msg->request == VHOST_USER_NONE) {

> +        g_main_loop_quit(rng->loop);

> +        return 1;

> +    }

> +

> +    return 0;

> +}

> +

> +static const VuDevIface vuiface = {

> +    .set_features = rng_set_features,

> +    .get_features = rng_get_features,

> +    .queue_set_started = vu_rng_queue_set_started,

> +    .process_msg = rng_process_msg,

> +};

> +

> +static gboolean hangup(gpointer user_data)

> +{

> +    GMainLoop *loop = (GMainLoop *) user_data;

> +

> +    g_printerr("%s: caught hangup/quit signal, quitting", __func__);

> +    g_main_loop_quit(loop);

> +    return true;

> +}

> +

> +static void panic(VuDev *dev, const char *msg)

> +{

> +    g_critical("%s\n", msg);

> +    exit(EXIT_FAILURE);

> +}

> +

> +/* Print vhost-user.json backend program capabilities */

> +static void print_capabilities(void)

> +{

> +    printf("{\n");

> +    printf("  \"type\": \"RNG\"\n");

> +    printf("  \"filename\": [ RNG source ]\n");

> +    printf("}\n");

> +}

> +

> +static GOptionEntry options[] = {

> +    { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path,

> +      "Location of vhost-user Unix domain socket, incompatible with --fd",

> +      "PATH" },

> +    { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd,

> +      "Specify the backend file-descriptor, incompatible with --socket-path",

> +      "FD" },

> +    { "period", 'p', 0, G_OPTION_ARG_INT, &period_ms,

> +      "Time needed (in ms) to transfer a maximum amount of byte", NULL },

> +    { "max-bytes", 'm', 0, G_OPTION_ARG_INT64, &max_bytes,

> +      "Maximum amount of byte that can be transferred in a period", NULL },

> +    { "filename", 'n', 0, G_OPTION_ARG_FILENAME, &source_path,

> +      "RNG source, defaults to /dev/urandom", "PATH" },

> +    { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap,

> +      "Output to stdout the backend capabilities in JSON format and exit",

> +      NULL},

> +    { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,

> +      "Be more verbose in output", NULL},

> +    { NULL }

> +};

> +

> +int main(int argc, char *argv[])

> +{

> +    GError *error = NULL;

> +    GOptionContext *context;

> +    g_autoptr(GSocket) socket = NULL;

> +    char default_source[] = "/dev/urandom";

> +    char *source = default_source;

> +    VuRNG rng;

> +

> +    context = g_option_context_new("vhost-user emulation of RNG device");

> +    g_option_context_add_main_entries(context, options, "vhost-user-rng");

> +    if (!g_option_context_parse(context, &argc, &argv, &error)) {

> +        g_printerr("option parsing failed: %s\n", error->message);

> +        exit(1);

> +    }

> +

> +    if (print_cap) {

> +        print_capabilities();

> +        exit(0);

> +    }

> +

> +    if (!socket_path && socket_fd < 0) {

> +        g_printerr("Please specify either --fd or --socket-path\n");

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    if (socket_path && socket_fd > 0) {

> +        g_printerr("Either --fd or --socket-path, not both\n");

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    if (max_bytes > INT64_MAX) {

> +        g_printerr("'max-bytes' parameter must be non-negative, "

> +                   "and less than 2^63\n");

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    if (period_ms <= 0) {

> +        g_printerr("'period' parameter expects a positive integer\n");

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    /*

> +     * Now create a vhost-user socket that we will receive messages

> +     * on. Once we have our handler set up we can enter the glib main

> +     * loop.

> +     */

> +    if (socket_path) {

> +        g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path);

> +        g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX,

> +                                                      G_SOCKET_TYPE_STREAM,

> +                                                      G_SOCKET_PROTOCOL_DEFAULT,

> +                                                      &error);

> +

> +        if (!g_socket_bind(bind_socket, addr, false, &error)) {

> +            g_printerr("Failed to bind to socket at %s (%s).\n",

> +                       socket_path, error->message);

> +            exit(EXIT_FAILURE);

> +        }

> +        if (!g_socket_listen(bind_socket, &error)) {

> +            g_printerr("Failed to listen on socket %s (%s).\n",

> +                       socket_path, error->message);

> +        }

> +        g_message("awaiting connection to %s", socket_path);

> +        socket = g_socket_accept(bind_socket, NULL, &error);

> +        if (!socket) {

> +            g_printerr("Failed to accept on socket %s (%s).\n",

> +                       socket_path, error->message);

> +        }

> +    } else {

> +        socket = g_socket_new_from_fd(socket_fd, &error);

> +        if (!socket) {

> +            g_printerr("Failed to connect to FD %d (%s).\n",

> +                       socket_fd, error->message);

> +            exit(EXIT_FAILURE);

> +        }

> +    }

> +

> +    /* Overwrite default RNG source with what user provided, if any */

> +    if (source_path) {

> +        source = source_path;

> +    }

> +

> +    source_fd = open(source, O_RDWR);

> +    if (source_fd < 0) {

> +        g_printerr("Failed to open RNG source %s\n", source);

> +        g_socket_close(socket, &error);

> +        unlink(socket_path);

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    /* catch exit signals */

> +    g_unix_signal_add(SIGHUP, hangup, rng.loop);

> +    g_unix_signal_add(SIGINT, hangup, rng.loop);

> +

> +    /*

> +     * Create the main loop first so all the various sources can be

> +     * added. As well as catching signals we need to ensure vug_init

> +     * can add it's GSource watches.

> +     */

> +    rng.loop = g_main_loop_new(NULL, FALSE);

> +

> +    if (!vug_init(&rng.dev, 1, g_socket_get_fd(socket),

> +                  panic, &vuiface)) {

> +        g_printerr("Failed to initialize libvhost-user-glib.\n");

> +        exit(EXIT_FAILURE);

> +    }

> +

> +    rng.quota_remaining = max_bytes;

> +    rng.activate_timer = true;

> +    pthread_mutex_init(&rng.rng_mutex, NULL);

> +    pthread_cond_init(&rng.rng_cond, NULL);

> +    setup_timer(&rng);

> +

> +    if (verbose) {

> +        g_info("period_ms: %d tv_sec: %ld tv_nsec: %lu\n",

> +               period_ms, rng.ts.it_value.tv_sec, rng.ts.it_value.tv_nsec);

> +    }

> +

> +    g_message("entering main loop, awaiting messages");

> +    g_main_loop_run(rng.loop);

> +    g_message("finished main loop, cleaning up");

> +

> +    g_main_loop_unref(rng.loop);

> +    vug_deinit(&rng.dev);

> +    timer_delete(rng.rate_limit_timer);

> +    close(source_fd);

> +    unlink(socket_path);

> +}

> diff --git a/tools/vhost-user-rng/meson.build b/tools/vhost-user-rng/meson.build

> new file mode 100644

> index 000000000000..4dc386daf335

> --- /dev/null

> +++ b/tools/vhost-user-rng/meson.build

> @@ -0,0 +1,10 @@

> +executable('vhost-user-rng', files(

> +  'main.c'),

> +  dependencies: [qemuutil, glib, gio, rt],

> +  install: true,

> +  install_dir: get_option('libexecdir'))

> +

> +configure_file(input: '50-qemu-rng.json.in',

> +               output: '50-qemu-rng.json',

> +               configuration: config_host,

> +               install_dir: qemu_datadir / 'vhost-user')

> -- 

> 2.25.1

> 

> -- 

> Stratos-dev mailing list

> Stratos-dev@op-lists.linaro.org

> https://op-lists.linaro.org/mailman/listinfo/stratos-dev
Mathieu Poirier June 2, 2021, 3:19 p.m. UTC | #2
On Tue, 1 Jun 2021 at 15:01, Joakim Bech <joakim.bech@linaro.org> wrote:
>

> On Tue, Jun 01, 2021 at 08:03:20PM +0000, Mathieu Poirier via Stratos-dev wrote:

> > This patch provides the vhost-user backend implementation to work

> > in tandem with the vhost-user-rng implementation of the QEMU VMM.

> >

> > It uses the vhost-user API so that other VMM can re-use the interface

> > without having to write the driver again.

> >

> > Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>

> > ---

> >  tools/meson.build                        |   8 +

> >  tools/vhost-user-rng/50-qemu-rng.json.in |   5 +

> >  tools/vhost-user-rng/main.c              | 403 +++++++++++++++++++++++

> >  tools/vhost-user-rng/meson.build         |  10 +

> >  4 files changed, 426 insertions(+)

> >  create mode 100644 tools/vhost-user-rng/50-qemu-rng.json.in

> >  create mode 100644 tools/vhost-user-rng/main.c

> >  create mode 100644 tools/vhost-user-rng/meson.build

> >

> > diff --git a/tools/meson.build b/tools/meson.build

> > index 3e5a0abfa29f..66b0a11fbb45 100644

> > --- a/tools/meson.build

> > +++ b/tools/meson.build

> > @@ -24,3 +24,11 @@ endif

> >  if have_virtiofsd

> >    subdir('virtiofsd')

> >  endif

> > +

> > +have_virtiorng = (have_system and

> > +    have_tools and

> > +    'CONFIG_LINUX' in config_host)

> > +

> > +if have_virtiorng

> > +  subdir('vhost-user-rng')

> > +endif

> > diff --git a/tools/vhost-user-rng/50-qemu-rng.json.in b/tools/vhost-user-rng/50-qemu-rng.json.in

> > new file mode 100644

> > index 000000000000..9186c3c6fe1d

> > --- /dev/null

> > +++ b/tools/vhost-user-rng/50-qemu-rng.json.in

> > @@ -0,0 +1,5 @@

> > +{

> > +  "description": "QEMU vhost-user-rng",

> > +  "type": "bridge",

> > +  "binary": "@libexecdir@/vhost-user-rng"

> > +}

> > diff --git a/tools/vhost-user-rng/main.c b/tools/vhost-user-rng/main.c

> > new file mode 100644

> > index 000000000000..ba7380b12659

> > --- /dev/null

> > +++ b/tools/vhost-user-rng/main.c

> > @@ -0,0 +1,403 @@

> > +/*

> > + * VIRTIO RNG Emulation via vhost-user

> > + *

> > + * Copyright (c) 2021 Mathieu Poirier <mathieu.poirier@linaro.org>

> > + *

> > + * SPDX-License-Identifier: GPL-2.0-or-later

> > + */

> > +

> > +#define G_LOG_DOMAIN "vhost-user-rng"

> > +#define G_LOG_USE_STRUCTURED 1

> > +

> > +#include <glib.h>

> > +#include <gio/gio.h>

> > +#include <gio/gunixsocketaddress.h>

> > +#include <glib-unix.h>

> > +#include <glib/gstdio.h>

> > +#include <pthread.h>

> > +#include <signal.h>

> > +#include <stdio.h>

> > +#include <stdbool.h>

> > +#include <string.h>

> > +#include <inttypes.h>

> > +#include <fcntl.h>

> > +#include <sys/ioctl.h>

> > +#include <sys/types.h>

> > +#include <sys/stat.h>

> > +#include <sys/mman.h>

> > +#include <time.h>

> > +#include <unistd.h>

> > +#include <endian.h>

> > +#include <assert.h>

> > +

> > +#include "qemu/cutils.h"

> > +#include "subprojects/libvhost-user/libvhost-user-glib.h"

> > +#include "subprojects/libvhost-user/libvhost-user.h"

> > +

> > +#ifndef container_of

> > +#define container_of(ptr, type, member) ({                      \

> > +        const typeof(((type *) 0)->member) * __mptr = (ptr);     \

> > +        (type *) ((char *) __mptr - offsetof(type, member)); })

> > +#endif

> > +

> > +typedef struct {

> > +    VugDev dev;

> > +    struct itimerspec ts;

> > +    timer_t rate_limit_timer;

> > +    pthread_mutex_t rng_mutex;

> > +    pthread_cond_t rng_cond;

> > +    int64_t quota_remaining;

> > +    bool activate_timer;

> > +    GMainLoop *loop;

> > +} VuRNG;

> > +

> > +static gboolean print_cap, verbose;

> > +static gchar *source_path, *socket_path;

> > +static gint source_fd, socket_fd = -1;

> > +

> > +/* Defaults tailored on virtio-rng.c */

> > +static uint32_t period_ms = 1 << 16;

> > +static uint64_t max_bytes = INT64_MAX;

> > +

> > +static void check_rate_limit(union sigval sv)

> > +{

> > +    VuRNG *rng = sv.sival_ptr;

> > +    bool wakeup = false;

> > +

> > +    pthread_mutex_lock(&rng->rng_mutex);

> > +    /*

> > +     * The timer has expired and the guest has used all available

> > +     * antropy, which means function vu_rng_handle_request() is waiting

> s/antropy/entropy/

>

> I also found 'antropy' at two more locations in this patch (that made me

> wonder).

>


Thanks for pointing that out, will fix for the next revision.

Mathieu

> > +     * on us.  As such wake it up once we're done here.

> > +     */

> > +    if (rng->quota_remaining == 0) {

> > +        wakeup = true;

> > +    }

> > +

> > +    /*

> > +     * Reset the antropy available to the guest and tell function

> > +     * vu_rng_handle_requests() to start the timer before using it.

> > +     */

> > +    rng->quota_remaining = max_bytes;

> > +    rng->activate_timer = true;

> > +    pthread_mutex_unlock(&rng->rng_mutex);

> > +

> > +    if (wakeup) {

> > +        pthread_cond_signal(&rng->rng_cond);

> > +    }

> > +}

> > +

> > +static void setup_timer(VuRNG *rng)

> > +{

> > +    struct sigevent sev;

> > +    int ret;

> > +

> > +    memset(&rng->ts, 0, sizeof(struct itimerspec));

> > +    rng->ts.it_value.tv_sec = period_ms / 1000;

> > +    rng->ts.it_value.tv_nsec = (period_ms % 1000) * 1000000;

> > +

> > +    /*

> > +     * Call function check_rate_limit() as if it was the start of

> > +     * a new thread when the timer expires.

> > +     */

> > +    sev.sigev_notify = SIGEV_THREAD;

> > +    sev.sigev_notify_function = check_rate_limit;

> > +    sev.sigev_value.sival_ptr = rng;

> > +    /* Needs to be NULL if defaults attributes are to be used. */

> > +    sev.sigev_notify_attributes = NULL;

> > +    ret = timer_create(CLOCK_MONOTONIC, &sev, &rng->rate_limit_timer);

> > +    if (ret < 0) {

> > +        fprintf(stderr, "timer_create() failed\n");

> > +    }

> > +

> > +}

> > +

> > +

> > +/* Virtio helpers */

> > +static uint64_t rng_get_features(VuDev *dev)

> > +{

> > +    if (verbose) {

> > +        g_info("%s: replying", __func__);

> > +    }

> > +    return 0;

> > +}

> > +

> > +static void rng_set_features(VuDev *dev, uint64_t features)

> > +{

> > +    if (verbose && features) {

> > +        g_autoptr(GString) s = g_string_new("Requested un-handled feature");

> > +        g_string_append_printf(s, " 0x%" PRIx64 "", features);

> > +        g_info("%s: %s", __func__, s->str);

> > +    }

> > +}

> > +

> > +static void vu_rng_handle_requests(VuDev *dev, int qidx)

> > +{

> > +    VuRNG *rng = container_of(dev, VuRNG, dev.parent);

> > +    VuVirtq *vq = vu_get_queue(dev, qidx);

> > +    VuVirtqElement *elem;

> > +    size_t to_read;

> > +    int len, ret;

> > +

> > +    for (;;) {

> > +        /* Get element in the vhost virtqueue */

> > +        elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement));

> > +        if (!elem) {

> > +            break;

> > +        }

> > +

> > +        /* Get the amount of antropy to read from the vhost server */

> > +        to_read = elem->in_sg[0].iov_len;

> > +

> > +        pthread_mutex_lock(&rng->rng_mutex);

> > +

> > +        /*

> > +         * We have consumed all entropy available for this time slice.

> > +         * Wait for the timer (check_rate_limit()) to tell us about the

> > +         * start of a new time slice.

> > +         */

> > +        if (rng->quota_remaining == 0) {

> > +            pthread_cond_wait(&rng->rng_cond, &rng->rng_mutex);

> > +        }

> > +

> > +        /* Start the timer if the last time slice has expired */

> > +        if (rng->activate_timer == true) {

> > +            rng->activate_timer = false;

> > +            ret = timer_settime(rng->rate_limit_timer, 0, &rng->ts, NULL);

> > +            if (ret < 0) {

> > +                fprintf(stderr, "timer_settime() failed\n");

> > +            }

> > +        }

> > +

> > +        /* Make sure we don't read more than it's available */

> > +        if (rng->quota_remaining < to_read) {

> > +            to_read = rng->quota_remaining;

> > +        }

> > +

> > +        len = read(source_fd, elem->in_sg[0].iov_base, to_read);

> > +

> > +        /* Simply return 0 if an error occurs */

> > +        if (len < 0) {

> > +            len = 0;

> > +        }

> > +

> > +        rng->quota_remaining -= len;

> > +

> > +        pthread_mutex_unlock(&rng->rng_mutex);

> > +

> > +        vu_queue_push(dev, vq, elem, len);

> > +        free(elem);

> > +    }

> > +

> > +    vu_queue_notify(dev, vq);

> > +}

> > +

> > +static void

> > +vu_rng_queue_set_started(VuDev *dev, int qidx, bool started)

> > +{

> > +    VuVirtq *vq = vu_get_queue(dev, qidx);

> > +

> > +    g_debug("queue started %d:%d\n", qidx, started);

> > +

> > +    if (!qidx) {

> > +        vu_set_queue_handler(dev, vq, started ? vu_rng_handle_requests : NULL);

> > +    }

> > +}

> > +

> > +/*

> > + * Any messages not handled here are processed by the libvhost library

> > + * itself.

> > + */

> > +static int rng_process_msg(VuDev *dev, VhostUserMsg *msg, int *do_reply)

> > +{

> > +    VuRNG *rng = container_of(dev, VuRNG, dev.parent);

> > +

> > +    if (msg->request == VHOST_USER_NONE) {

> > +        g_main_loop_quit(rng->loop);

> > +        return 1;

> > +    }

> > +

> > +    return 0;

> > +}

> > +

> > +static const VuDevIface vuiface = {

> > +    .set_features = rng_set_features,

> > +    .get_features = rng_get_features,

> > +    .queue_set_started = vu_rng_queue_set_started,

> > +    .process_msg = rng_process_msg,

> > +};

> > +

> > +static gboolean hangup(gpointer user_data)

> > +{

> > +    GMainLoop *loop = (GMainLoop *) user_data;

> > +

> > +    g_printerr("%s: caught hangup/quit signal, quitting", __func__);

> > +    g_main_loop_quit(loop);

> > +    return true;

> > +}

> > +

> > +static void panic(VuDev *dev, const char *msg)

> > +{

> > +    g_critical("%s\n", msg);

> > +    exit(EXIT_FAILURE);

> > +}

> > +

> > +/* Print vhost-user.json backend program capabilities */

> > +static void print_capabilities(void)

> > +{

> > +    printf("{\n");

> > +    printf("  \"type\": \"RNG\"\n");

> > +    printf("  \"filename\": [ RNG source ]\n");

> > +    printf("}\n");

> > +}

> > +

> > +static GOptionEntry options[] = {

> > +    { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &socket_path,

> > +      "Location of vhost-user Unix domain socket, incompatible with --fd",

> > +      "PATH" },

> > +    { "fd", 'f', 0, G_OPTION_ARG_INT, &socket_fd,

> > +      "Specify the backend file-descriptor, incompatible with --socket-path",

> > +      "FD" },

> > +    { "period", 'p', 0, G_OPTION_ARG_INT, &period_ms,

> > +      "Time needed (in ms) to transfer a maximum amount of byte", NULL },

> > +    { "max-bytes", 'm', 0, G_OPTION_ARG_INT64, &max_bytes,

> > +      "Maximum amount of byte that can be transferred in a period", NULL },

> > +    { "filename", 'n', 0, G_OPTION_ARG_FILENAME, &source_path,

> > +      "RNG source, defaults to /dev/urandom", "PATH" },

> > +    { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &print_cap,

> > +      "Output to stdout the backend capabilities in JSON format and exit",

> > +      NULL},

> > +    { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,

> > +      "Be more verbose in output", NULL},

> > +    { NULL }

> > +};

> > +

> > +int main(int argc, char *argv[])

> > +{

> > +    GError *error = NULL;

> > +    GOptionContext *context;

> > +    g_autoptr(GSocket) socket = NULL;

> > +    char default_source[] = "/dev/urandom";

> > +    char *source = default_source;

> > +    VuRNG rng;

> > +

> > +    context = g_option_context_new("vhost-user emulation of RNG device");

> > +    g_option_context_add_main_entries(context, options, "vhost-user-rng");

> > +    if (!g_option_context_parse(context, &argc, &argv, &error)) {

> > +        g_printerr("option parsing failed: %s\n", error->message);

> > +        exit(1);

> > +    }

> > +

> > +    if (print_cap) {

> > +        print_capabilities();

> > +        exit(0);

> > +    }

> > +

> > +    if (!socket_path && socket_fd < 0) {

> > +        g_printerr("Please specify either --fd or --socket-path\n");

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    if (socket_path && socket_fd > 0) {

> > +        g_printerr("Either --fd or --socket-path, not both\n");

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    if (max_bytes > INT64_MAX) {

> > +        g_printerr("'max-bytes' parameter must be non-negative, "

> > +                   "and less than 2^63\n");

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    if (period_ms <= 0) {

> > +        g_printerr("'period' parameter expects a positive integer\n");

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    /*

> > +     * Now create a vhost-user socket that we will receive messages

> > +     * on. Once we have our handler set up we can enter the glib main

> > +     * loop.

> > +     */

> > +    if (socket_path) {

> > +        g_autoptr(GSocketAddress) addr = g_unix_socket_address_new(socket_path);

> > +        g_autoptr(GSocket) bind_socket = g_socket_new(G_SOCKET_FAMILY_UNIX,

> > +                                                      G_SOCKET_TYPE_STREAM,

> > +                                                      G_SOCKET_PROTOCOL_DEFAULT,

> > +                                                      &error);

> > +

> > +        if (!g_socket_bind(bind_socket, addr, false, &error)) {

> > +            g_printerr("Failed to bind to socket at %s (%s).\n",

> > +                       socket_path, error->message);

> > +            exit(EXIT_FAILURE);

> > +        }

> > +        if (!g_socket_listen(bind_socket, &error)) {

> > +            g_printerr("Failed to listen on socket %s (%s).\n",

> > +                       socket_path, error->message);

> > +        }

> > +        g_message("awaiting connection to %s", socket_path);

> > +        socket = g_socket_accept(bind_socket, NULL, &error);

> > +        if (!socket) {

> > +            g_printerr("Failed to accept on socket %s (%s).\n",

> > +                       socket_path, error->message);

> > +        }

> > +    } else {

> > +        socket = g_socket_new_from_fd(socket_fd, &error);

> > +        if (!socket) {

> > +            g_printerr("Failed to connect to FD %d (%s).\n",

> > +                       socket_fd, error->message);

> > +            exit(EXIT_FAILURE);

> > +        }

> > +    }

> > +

> > +    /* Overwrite default RNG source with what user provided, if any */

> > +    if (source_path) {

> > +        source = source_path;

> > +    }

> > +

> > +    source_fd = open(source, O_RDWR);

> > +    if (source_fd < 0) {

> > +        g_printerr("Failed to open RNG source %s\n", source);

> > +        g_socket_close(socket, &error);

> > +        unlink(socket_path);

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    /* catch exit signals */

> > +    g_unix_signal_add(SIGHUP, hangup, rng.loop);

> > +    g_unix_signal_add(SIGINT, hangup, rng.loop);

> > +

> > +    /*

> > +     * Create the main loop first so all the various sources can be

> > +     * added. As well as catching signals we need to ensure vug_init

> > +     * can add it's GSource watches.

> > +     */

> > +    rng.loop = g_main_loop_new(NULL, FALSE);

> > +

> > +    if (!vug_init(&rng.dev, 1, g_socket_get_fd(socket),

> > +                  panic, &vuiface)) {

> > +        g_printerr("Failed to initialize libvhost-user-glib.\n");

> > +        exit(EXIT_FAILURE);

> > +    }

> > +

> > +    rng.quota_remaining = max_bytes;

> > +    rng.activate_timer = true;

> > +    pthread_mutex_init(&rng.rng_mutex, NULL);

> > +    pthread_cond_init(&rng.rng_cond, NULL);

> > +    setup_timer(&rng);

> > +

> > +    if (verbose) {

> > +        g_info("period_ms: %d tv_sec: %ld tv_nsec: %lu\n",

> > +               period_ms, rng.ts.it_value.tv_sec, rng.ts.it_value.tv_nsec);

> > +    }

> > +

> > +    g_message("entering main loop, awaiting messages");

> > +    g_main_loop_run(rng.loop);

> > +    g_message("finished main loop, cleaning up");

> > +

> > +    g_main_loop_unref(rng.loop);

> > +    vug_deinit(&rng.dev);

> > +    timer_delete(rng.rate_limit_timer);

> > +    close(source_fd);

> > +    unlink(socket_path);

> > +}

> > diff --git a/tools/vhost-user-rng/meson.build b/tools/vhost-user-rng/meson.build

> > new file mode 100644

> > index 000000000000..4dc386daf335

> > --- /dev/null

> > +++ b/tools/vhost-user-rng/meson.build

> > @@ -0,0 +1,10 @@

> > +executable('vhost-user-rng', files(

> > +  'main.c'),

> > +  dependencies: [qemuutil, glib, gio, rt],

> > +  install: true,

> > +  install_dir: get_option('libexecdir'))

> > +

> > +configure_file(input: '50-qemu-rng.json.in',

> > +               output: '50-qemu-rng.json',

> > +               configuration: config_host,

> > +               install_dir: qemu_datadir / 'vhost-user')

> > --

> > 2.25.1

> >

> > --

> > Stratos-dev mailing list

> > Stratos-dev@op-lists.linaro.org

> > https://op-lists.linaro.org/mailman/listinfo/stratos-dev