From: Baptiste Reynal <b.rey...@virtualopensystems.com> This patch introduces a new socket for QEMU, called multi-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. Signed-off-by: Baptiste Reynal <b.rey...@virtualopensystems.com> --- backends/Makefile.objs | 2 + backends/multi-socket.c | 353 ++++++++++++++++++++++++++++++++++++++++++++ include/qemu/multi-socket.h | 124 ++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 backends/multi-socket.c create mode 100644 include/qemu/multi-socket.h 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..36bee3a --- /dev/null +++ b/backends/multi-socket.c @@ -0,0 +1,353 @@ +/* + * QEMU Multi Client socket + * + * Copyright (C) 2015 - Virtual Open Systems + * + * Author: Baptiste Reynal <b.rey...@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" + +typedef struct MSHandler { + char *name; + void (*read)(MSClient *client, const char *message, void *opaque); + void *opaque; + + QLIST_ENTRY (MSHandler) next; +} MSHandler; + +typedef struct MSRegHandler { + void (*reg)(MSClient *client, void *opaque); + void *opaque; + + QLIST_ENTRY (MSRegHandler) next; +} MSRegHandler; + +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; + } + + if (client->rcvfds) { + 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; + } + } + printf("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)) { + printf("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.rey...@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 -- 1.9.1