[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Stratos-dev] [PATCH 3/5] vhost-user-rng: backend: Add RNG vhost-use
From: |
Mathieu Poirier |
Subject: |
Re: [Stratos-dev] [PATCH 3/5] vhost-user-rng: backend: Add RNG vhost-user daemon implementation |
Date: |
Wed, 2 Jun 2021 09:19:43 -0600 |
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
- [PATCH 0/5] virtio: Add vhost-user based RNG service, Mathieu Poirier, 2021/06/01
- [PATCH 1/5] vhost-user-rng: Add vhost-user-rng implementation, Mathieu Poirier, 2021/06/01
- [PATCH 2/5] vhost-user-rng-pci: Add vhost-user-rng-pci implementation, Mathieu Poirier, 2021/06/01
- [PATCH 4/5] docs: Add documentation for vhost based RNG implementation, Mathieu Poirier, 2021/06/01
- [PATCH 5/5] MAINTAINERS: Add maintainer for vhost-user RNG implementation, Mathieu Poirier, 2021/06/01
- [PATCH 3/5] vhost-user-rng: backend: Add RNG vhost-user daemon implementation, Mathieu Poirier, 2021/06/01
- Message not available