diff mbox

[1/1] backend: multi-client-socket

Message ID 1458292366-13723-1-git-send-email-b.reynal@virtualopensystems.com
State New
Headers show

Commit Message

Baptiste Reynal March 18, 2016, 9:12 a.m. UTC
This patch introduces a new socket for QEMU, called multi-client-socket. This
socket allows multiple QEMU instances to communicate by sharing messages
and file descriptors.

A socket can be instantiated with the following parameters:
-object multi-socket-backend,id=<id>,path=<socket_path>,listen=<on/off>

If listen is set, the socket will act as a listener and register new
clients.

This patch is a follow-up to "[RFC PATCH 0/8] Towards an Heterogeneous QEMU":
https://lists.gnu.org/archive/html/qemu-devel/2015-10/msg00171.html

This work has been sponsored by Huawei Technologies Duesseldorf GmbH.

Signed-off-by: Baptiste Reynal <b.reynal@virtualopensystems.com>
---
 backends/Makefile.objs      |   2 +
 backends/multi-socket.c     | 355 ++++++++++++++++++++++++++++++++++++++++++++
 include/qemu/multi-socket.h | 124 ++++++++++++++++
 3 files changed, 481 insertions(+)
 create mode 100644 backends/multi-socket.c
 create mode 100644 include/qemu/multi-socket.h

Comments

Stefan Hajnoczi March 31, 2016, 9:08 a.m. UTC | #1
On Fri, Mar 18, 2016 at 10:12:46AM +0100, Baptiste Reynal wrote:
> This patch introduces a new socket for QEMU, called multi-client-socket. This
> socket allows multiple QEMU instances to communicate by sharing messages
> and file descriptors.
> 
> A socket can be instantiated with the following parameters:
> -object multi-socket-backend,id=<id>,path=<socket_path>,listen=<on/off>
> 
> If listen is set, the socket will act as a listener and register new
> clients.
> 
> This patch is a follow-up to "[RFC PATCH 0/8] Towards an Heterogeneous QEMU":
> https://lists.gnu.org/archive/html/qemu-devel/2015-10/msg00171.html
> 
> This work has been sponsored by Huawei Technologies Duesseldorf GmbH.
> 
> Signed-off-by: Baptiste Reynal <b.reynal@virtualopensystems.com>
> ---
>  backends/Makefile.objs      |   2 +
>  backends/multi-socket.c     | 355 ++++++++++++++++++++++++++++++++++++++++++++
>  include/qemu/multi-socket.h | 124 ++++++++++++++++
>  3 files changed, 481 insertions(+)
>  create mode 100644 backends/multi-socket.c
>  create mode 100644 include/qemu/multi-socket.h

Is it possible to reuse QEMU's UNIX domain socket and fd passing code?
For example, take a look at io/channel-socket.c.
Baptiste Reynal April 8, 2016, 1:18 p.m. UTC | #2
I wasn't aware of this new framework when I started the development of
this socket. For sure I'll have a deeper look and see what can be
shared for the next version.

On Thu, Mar 31, 2016 at 11:08 AM, Stefan Hajnoczi <stefanha@gmail.com> wrote:
> On Fri, Mar 18, 2016 at 10:12:46AM +0100, Baptiste Reynal wrote:
>> This patch introduces a new socket for QEMU, called multi-client-socket. This
>> socket allows multiple QEMU instances to communicate by sharing messages
>> and file descriptors.
>>
>> A socket can be instantiated with the following parameters:
>> -object multi-socket-backend,id=<id>,path=<socket_path>,listen=<on/off>
>>
>> If listen is set, the socket will act as a listener and register new
>> clients.
>>
>> This patch is a follow-up to "[RFC PATCH 0/8] Towards an Heterogeneous QEMU":
>> https://lists.gnu.org/archive/html/qemu-devel/2015-10/msg00171.html
>>
>> This work has been sponsored by Huawei Technologies Duesseldorf GmbH.
>>
>> Signed-off-by: Baptiste Reynal <b.reynal@virtualopensystems.com>
>> ---
>>  backends/Makefile.objs      |   2 +
>>  backends/multi-socket.c     | 355 ++++++++++++++++++++++++++++++++++++++++++++
>>  include/qemu/multi-socket.h | 124 ++++++++++++++++
>>  3 files changed, 481 insertions(+)
>>  create mode 100644 backends/multi-socket.c
>>  create mode 100644 include/qemu/multi-socket.h
>
> Is it possible to reuse QEMU's UNIX domain socket and fd passing code?
> For example, take a look at io/channel-socket.c.
diff mbox

Patch

diff --git a/backends/Makefile.objs b/backends/Makefile.objs
index 31a3a89..689eac3 100644
--- a/backends/Makefile.objs
+++ b/backends/Makefile.objs
@@ -9,3 +9,5 @@  common-obj-$(CONFIG_TPM) += tpm.o
 
 common-obj-y += hostmem.o hostmem-ram.o
 common-obj-$(CONFIG_LINUX) += hostmem-file.o
+
+common-obj-y += multi-socket.o
diff --git a/backends/multi-socket.c b/backends/multi-socket.c
new file mode 100644
index 0000000..2cfbb50
--- /dev/null
+++ b/backends/multi-socket.c
@@ -0,0 +1,355 @@ 
+/*
+ * QEMU Multi Client socket
+ *
+ * Copyright (C) 2015 - Virtual Open Systems
+ *
+ * Author: Baptiste Reynal <b.reynal@virtualopensystems.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/multi-socket.h"
+#include "qemu/error-report.h"
+
+typedef struct MSHandler MSHandler;
+typedef struct MSRegHandler MSRegHandler;
+
+struct MSHandler {
+    char *name;
+    void (*read)(MSClient *client, const char *message, void *opaque);
+    void *opaque;
+
+    QLIST_ENTRY(MSHandler) next;
+};
+
+struct MSRegHandler {
+    void (*reg)(MSClient *client, void *opaque);
+    void *opaque;
+
+    QLIST_ENTRY(MSRegHandler) next;
+};
+
+static void multi_socket_get_fds(MSClient *client, struct msghdr msg)
+{
+    struct cmsghdr *cmsg;
+
+    /* process fds */
+    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+        int fd_size;
+
+        if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
+                cmsg->cmsg_level != SOL_SOCKET ||
+                cmsg->cmsg_type != SCM_RIGHTS) {
+            continue;
+        }
+
+        fd_size = cmsg->cmsg_len - CMSG_LEN(0);
+
+        if (!fd_size) {
+            continue;
+        }
+
+        g_free(client->rcvfds);
+
+        client->rcvfds_num = fd_size / sizeof(int);
+        client->rcvfds = g_malloc(fd_size);
+        memcpy(client->rcvfds, CMSG_DATA(cmsg), fd_size);
+    }
+}
+
+static gboolean
+multi_socket_read_handler(GIOChannel *channel, GIOCondition cond, void *opaque)
+{
+    MSClient *client = (MSClient *) opaque;
+    MSBackend *backend = client->backend;
+
+    char message[BUFFER_SIZE];
+    struct MSHandler *h;
+
+    struct msghdr msg = { NULL, };
+    struct iovec iov[1];
+    union {
+        struct cmsghdr cmsg;
+        char control[CMSG_SPACE(sizeof(int) * MAX_FDS)];
+    } msg_control;
+    int flags = 0;
+    ssize_t ret;
+
+    iov[0].iov_base = message;
+    iov[0].iov_len = BUFFER_SIZE;
+
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = &msg_control;
+    msg.msg_controllen = sizeof(msg_control);
+
+    ret = recvmsg(client->fd, &msg, flags);
+
+    if (ret > 0) {
+        multi_socket_get_fds(client, msg);
+
+        /* handler callback */
+        QLIST_FOREACH(h, &backend->handlers, next) {
+            if (!strncmp(h->name, message, strlen(h->name))) {
+                h->read(client, message + strlen(h->name) + 1, h->opaque);
+                return TRUE;
+            }
+        }
+        error_report("Unrecognized message: %s", message);
+    }
+
+    return FALSE;
+}
+
+void multi_socket_add_reg_handler(MSBackend *backend,
+        void (*reg)(MSClient *client, void *opaque), void *opaque)
+{
+    struct MSRegHandler *h;
+
+    h = g_malloc(sizeof(struct MSRegHandler));
+
+    h->reg = reg;
+    h->opaque = opaque;
+
+    QLIST_INSERT_HEAD(&backend->reg_handlers, h, next);
+}
+
+void multi_socket_add_handler(MSBackend *backend,
+        const char *name,
+        void (*read)(MSClient *c, const char *message, void *opaque),
+        void *opaque)
+{
+    struct MSHandler *h;
+
+    /* check that the handler name is not taken */
+    QLIST_FOREACH(h, &backend->handlers, next) {
+        if (!strcmp(h->name, name)) {
+            error_report("Handler %s already exists", name);
+            return;
+        }
+    }
+
+    h = g_malloc(sizeof(struct MSHandler));
+
+    h->name = g_strdup(name);
+    h->read = read;
+    h->opaque = opaque;
+
+    QLIST_INSERT_HEAD(&backend->handlers, h, next);
+}
+
+static void multi_socket_init_client(MSBackend *backend,
+        MSClient *client, int fd, GIOFunc handler)
+{
+    client->backend = backend;
+    client->fd = fd;
+    client->chan = g_io_channel_unix_new(fd);
+    client->tag = g_io_add_watch(client->chan, G_IO_IN, handler, client);
+
+    g_io_channel_set_encoding(client->chan, NULL, NULL);
+    g_io_channel_set_buffered(client->chan, FALSE);
+}
+
+int multi_socket_send_fds_to(MSClient *client, int *fds, int count,
+        const char *message, int size)
+{
+    struct msghdr msgh;
+    struct iovec iov;
+    int r;
+
+    size_t fd_size = count * sizeof(int);
+    char control[CMSG_SPACE(fd_size)];
+    struct cmsghdr *cmsg;
+
+    memset(&msgh, 0, sizeof(msgh));
+    memset(control, 0, sizeof(control));
+
+    /* set the payload */
+    iov.iov_base = (uint8_t *) message;
+    iov.iov_len = size;
+
+    msgh.msg_iov = &iov;
+    msgh.msg_iovlen = 1;
+
+    msgh.msg_control = control;
+    msgh.msg_controllen = sizeof(control);
+
+    cmsg = CMSG_FIRSTHDR(&msgh);
+
+    cmsg->cmsg_len = CMSG_LEN(fd_size);
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    memcpy(CMSG_DATA(cmsg), fds, fd_size);
+
+    do {
+        r = sendmsg(client->fd, &msgh, 0);
+    } while (r < 0 && errno == EINTR);
+
+    return r;
+}
+
+int multi_socket_write_to(MSClient *client, const char *message, int size)
+{
+    return multi_socket_send_fds_to(client, 0, 0, message, size);
+}
+
+int multi_socket_get_fds_num_from(MSClient *client)
+{
+    return client->rcvfds_num;
+}
+
+int multi_socket_get_fds_from(MSClient *client, int *fds)
+{
+    memcpy(fds, client->rcvfds, client->rcvfds_num * sizeof(int));
+
+    return client->rcvfds_num;
+}
+
+static void multi_socket_add_client(MSBackend *backend, int fd)
+{
+    MSClient *c = g_malloc(sizeof(MSClient));
+    MSRegHandler *h;
+
+    multi_socket_init_client(backend, c, fd, multi_socket_read_handler);
+    QLIST_FOREACH(h, &backend->reg_handlers, next) {
+        h->reg(c, h->opaque);
+    }
+
+    QLIST_INSERT_HEAD(&backend->clients, c, next);
+}
+
+static gboolean
+multi_socket_accept(GIOChannel *channel, GIOCondition cond, void *opaque)
+{
+    MSClient *client = (MSClient *) opaque;
+    MSBackend *backend = client->backend;
+
+    struct sockaddr_un uaddr;
+    socklen_t len;
+    int fd;
+
+    len = sizeof(uaddr);
+
+    fd = qemu_accept(backend->listener.fd, (struct sockaddr *) &uaddr, &len);
+
+    if (fd > 0) {
+        multi_socket_add_client(backend, fd);
+        return true;
+    } else {
+        perror("Error creating socket.");
+        return false;
+    }
+}
+
+static void multi_socket_init_socket(MSBackend *backend)
+{
+    int fd;
+
+    backend->addr = g_new0(SocketAddress, 1);
+    backend->addr->kind = SOCKET_ADDRESS_KIND_UNIX;
+    backend->addr->q_unix = g_new0(UnixSocketAddress, 1);
+    /* TODO change name with real path */
+    backend->addr->q_unix->path = g_strdup(backend->path);
+
+    if (backend->listen) {
+        fd = socket_listen(backend->addr, NULL);
+
+        if (fd < 0) {
+            perror("Error: Impossible to open socket.");
+            exit(-1);
+        }
+
+        multi_socket_init_client(backend, &backend->listener, fd,
+                multi_socket_accept);
+    } else {
+        fd = socket_connect(backend->addr, NULL, NULL, NULL);
+
+        if (fd < 0) {
+            perror("Error: Unavailable socket server.");
+            exit(-1);
+        }
+
+        multi_socket_init_client(backend, &backend->listener,
+                fd, multi_socket_read_handler);
+    }
+}
+
+static void multi_socket_backend_complete(UserCreatable *uc, Error **errp)
+{
+    MSBackend *backend = MULTI_SOCKET_BACKEND(uc);
+
+    QLIST_INIT(&backend->clients);
+    QLIST_INIT(&backend->handlers);
+
+    multi_socket_init_socket(backend);
+}
+
+static void multi_socket_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+    ucc->complete = multi_socket_backend_complete;
+}
+
+static bool multi_socket_backend_get_listen(Object *o, Error **errp)
+{
+    MSBackend *backend = MULTI_SOCKET_BACKEND(o);
+
+    return backend->listen;
+}
+
+static void multi_socket_backend_set_listen(Object *o, bool value, Error **errp)
+{
+    MSBackend *backend = MULTI_SOCKET_BACKEND(o);
+
+    backend->listen = value;
+}
+
+static char *multi_socket_get_path(Object *o, Error **errp)
+{
+    MSBackend *backend = MULTI_SOCKET_BACKEND(o);
+
+    return g_strdup(backend->path);
+}
+
+static void multi_socket_set_path(Object *o, const char *str, Error **errp)
+{
+    MSBackend *backend = MULTI_SOCKET_BACKEND(o);
+
+    if (str == NULL || str[0] == 0) {
+        perror("Error: Socket path is empty.");
+        exit(-1);
+    }
+
+    backend->path = g_strdup(str);
+}
+
+static void multi_socket_instance_init(Object *o)
+{
+    object_property_add_bool(o, "listen",
+                        multi_socket_backend_get_listen,
+                        multi_socket_backend_set_listen, NULL);
+    object_property_add_str(o, "path", multi_socket_get_path,
+                        multi_socket_set_path, NULL);
+}
+
+static const TypeInfo multi_socket_backend_info = {
+    .name = TYPE_MULTI_SOCKET_BACKEND,
+    .parent = TYPE_OBJECT,
+    .class_size = sizeof(MSBackendClass),
+    .class_init = multi_socket_class_init,
+    .instance_size = sizeof(MSBackend),
+    .instance_init = multi_socket_instance_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static void register_types(void)
+{
+    type_register_static(&multi_socket_backend_info);
+}
+
+type_init(register_types);
diff --git a/include/qemu/multi-socket.h b/include/qemu/multi-socket.h
new file mode 100644
index 0000000..dee866a
--- /dev/null
+++ b/include/qemu/multi-socket.h
@@ -0,0 +1,124 @@ 
+/*
+ * QEMU Multi Client socket
+ *
+ * Copyright (C) 2015 - Virtual Open Systems
+ *
+ * Author: Baptiste Reynal <b.reynal@virtualopensystems.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+#ifndef QEMU_MS_H
+#define QEMU_MS_H
+
+#include "qemu-common.h"
+#include "qemu/queue.h"
+#include "qemu/sockets.h"
+#include "qom/object.h"
+#include "qom/object_interfaces.h"
+
+#define TYPE_MULTI_SOCKET_BACKEND "multi-socket-backend"
+#define MULTI_SOCKET_BACKEND(obj) \
+    OBJECT_CHECK(MSBackend, (obj), TYPE_MULTI_SOCKET_BACKEND)
+#define MULTI_SOCKET_BACKEND_GET_CLASS(obj) \
+    OBJECT_GET_CLASS(MSBackendClass, (obj), TYPE_MULTI_SOCKET_BACKEND)
+#define MULTI_SOCKET_BACKEND_CLASS(klass) \
+    OBJECT_CLASS_CHECK(MSBackendClass, (klass), TYPE_MULTI_SOCKET_BACKEND)
+
+#define MAX_FDS     32
+#define BUFFER_SIZE 32
+
+typedef struct MSBackend MSBackend;
+typedef struct MSBackendClass MSBackendClass;
+typedef struct MSClient MSClient;
+
+struct MSClient {
+    /* private */
+    int fd;
+    MSBackend *backend;
+    GIOChannel *chan;
+    guint tag;
+
+    int *rcvfds;
+    int rcvfds_num;
+
+    QLIST_ENTRY(MSClient) next;
+};
+
+struct MSBackendClass {
+    /* private */
+    ObjectClass parent_class;
+};
+
+struct MSBackend {
+    /* private */
+    Object parent;
+
+    /* protected */
+    char *path;
+    SocketAddress *addr;
+
+    QLIST_HEAD(clients_head, MSClient) clients;
+    QLIST_HEAD(reg_handlers_head, MSRegHandler) reg_handlers;
+    QLIST_HEAD(handlers_head, MSHandler) handlers;
+
+    bool listen;
+    MSClient listener;
+};
+
+/* Callback method called each time a client is registered to the server.
+ * @backend: socket server
+ * @reg: callback function
+ * @opaque: optionnal data passed to register function
+ */
+void multi_socket_add_reg_handler(MSBackend *backend,
+        void (*reg)(MSClient *client, void *opaque),
+        void *opaque);
+
+/* Attach a handler to the socket. "read" function will be called if a string
+ * begining with "name" is received over the socket. Payload can be attached
+ * next to name and will be passed to the "read" function as "message"
+ * parameter.
+ *
+ * @backend: multi-client socket
+ * @name: name of the handler (should be unique for the socket)
+ * @read: callback function
+ * @opaque:optionnal datas passed to read function
+ */
+void multi_socket_add_handler(MSBackend *backend, const char *name,
+        void (*read)(MSClient *client, const char *message, void *opaque),
+        void *opaque);
+
+/* Send file descriptors over the socket.
+ *
+ * @client: client to whom send the message
+ * @fds: file descriptors array to send
+ * @count: size of the array
+ * @message: attached message
+ * @size: size of the message
+ */
+int multi_socket_send_fds_to(MSClient *client, int *fds, int count,
+        const char *message, int size);
+
+/* Send message over the socket
+ *
+ * @client: client to whom send the message
+ * @message: attached message
+ * @size: size of the message
+ */
+int multi_socket_write_to(MSClient *client, const char *message, int size);
+
+/* Get fds size received with the last message.
+ *
+ * @client: client who sent the message
+ */
+int multi_socket_get_fds_num_from(MSClient *client);
+
+/* Get the fds received with the last message.
+ *
+ * @client: client who sent the message
+ * @fds: int array to fill with the fds
+ */
+int multi_socket_get_fds_from(MSClient *client, int *fds);
+
+#endif