/*- * BSD LICENSE * * Copyright(c) 2010-2016 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fd_man.h" #include "vhost.h" #include "vhost_user.h" TAILQ_HEAD(vhost_user_connection_list, vhost_user_connection); /* * Every time rte_vhost_driver_register() is invoked, an associated * vhost_user_socket struct will be created. */ struct vhost_user_socket { struct vhost_user_connection_list conn_list; pthread_mutex_t conn_mutex; char *path; int socket_fd; struct sockaddr_un un; bool is_server; bool reconnect; bool dequeue_zero_copy; /* * The "supported_features" indicates the feature bits the * vhost driver supports. The "features" indicates the feature * bits after the rte_vhost_driver_features_disable/enable(). * It is also the final feature bits used for vhost-user * features negotiation. */ uint64_t supported_features; uint64_t features; struct vhost_device_ops const *notify_ops; }; struct vhost_user_connection { struct vhost_user_socket *vsocket; int connfd; int vid; TAILQ_ENTRY(vhost_user_connection) next; }; #define MAX_VHOST_SOCKET 1024 struct vhost_user { struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET]; struct fdset fdset; int vsocket_cnt; pthread_mutex_t mutex; }; #define MAX_VIRTIO_BACKLOG 128 static void vhost_user_server_new_connection(int fd, void *data, int *remove); static void vhost_user_read_cb(int fd, void *dat, int *remove); static int create_unix_socket(struct vhost_user_socket *vsocket); static int vhost_user_start_client(struct vhost_user_socket *vsocket); static struct vhost_user vhost_user = { .fdset = { .fd = { [0 ... MAX_FDS - 1] = {-1, NULL, NULL, NULL, 0} }, .fd_mutex = PTHREAD_MUTEX_INITIALIZER, .num = 0 }, .vsocket_cnt = 0, .mutex = PTHREAD_MUTEX_INITIALIZER, }; /* return bytes# of read on success or negative val on failure. */ int read_fd_message(int sockfd, char *buf, int buflen, int *fds, int fd_num) { struct iovec iov; struct msghdr msgh; size_t fdsize = fd_num * sizeof(int); char control[CMSG_SPACE(fdsize)]; struct cmsghdr *cmsg; int ret; memset(&msgh, 0, sizeof(msgh)); iov.iov_base = buf; iov.iov_len = buflen; msgh.msg_iov = &iov; msgh.msg_iovlen = 1; msgh.msg_control = control; msgh.msg_controllen = sizeof(control); ret = recvmsg(sockfd, &msgh, 0); if (ret <= 0) { RTE_LOG(ERR, VHOST_CONFIG, "recvmsg failed\n"); return ret; } if (msgh.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { RTE_LOG(ERR, VHOST_CONFIG, "truncted msg\n"); return -1; } for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg)) { if ((cmsg->cmsg_level == SOL_SOCKET) && (cmsg->cmsg_type == SCM_RIGHTS)) { memcpy(fds, CMSG_DATA(cmsg), fdsize); break; } } return ret; } int send_fd_message(int sockfd, char *buf, int buflen, int *fds, int fd_num) { struct iovec iov; struct msghdr msgh; size_t fdsize = fd_num * sizeof(int); char control[CMSG_SPACE(fdsize)]; struct cmsghdr *cmsg; int ret; memset(&msgh, 0, sizeof(msgh)); iov.iov_base = buf; iov.iov_len = buflen; msgh.msg_iov = &iov; msgh.msg_iovlen = 1; if (fds && fd_num > 0) { msgh.msg_control = control; msgh.msg_controllen = sizeof(control); cmsg = CMSG_FIRSTHDR(&msgh); if (cmsg == NULL) { RTE_LOG(ERR, VHOST_CONFIG, "cmsg == NULL\n"); errno = EINVAL; return -1; } cmsg->cmsg_len = CMSG_LEN(fdsize); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), fds, fdsize); } else { msgh.msg_control = NULL; msgh.msg_controllen = 0; } do { ret = sendmsg(sockfd, &msgh, 0); } while (ret < 0 && errno == EINTR); if (ret < 0) { RTE_LOG(ERR, VHOST_CONFIG, "sendmsg error\n"); return ret; } return ret; } static void vhost_user_add_connection(int fd, struct vhost_user_socket *vsocket) { int vid; size_t size; struct vhost_user_connection *conn; int ret; conn = malloc(sizeof(*conn)); if (conn == NULL) { close(fd); return; } vid = vhost_new_device(vsocket->features); if (vid == -1) { goto err; } size = strnlen(vsocket->path, PATH_MAX); vhost_set_ifname(vid, vsocket->path, size); if (vsocket->dequeue_zero_copy) vhost_enable_dequeue_zero_copy(vid); RTE_LOG(INFO, VHOST_CONFIG, "new device, handle is %d\n", vid); if (vsocket->notify_ops->new_connection) { ret = vsocket->notify_ops->new_connection(vid); if (ret < 0) { RTE_LOG(ERR, VHOST_CONFIG, "failed to add vhost user connection with fd %d\n", fd); goto err; } } conn->connfd = fd; conn->vsocket = vsocket; conn->vid = vid; ret = fdset_add(&vhost_user.fdset, fd, vhost_user_read_cb, NULL, conn); if (ret < 0) { RTE_LOG(ERR, VHOST_CONFIG, "failed to add fd %d into vhost server fdset\n", fd); if (vsocket->notify_ops->destroy_connection) vsocket->notify_ops->destroy_connection(conn->vid); goto err; } pthread_mutex_lock(&vsocket->conn_mutex); TAILQ_INSERT_TAIL(&vsocket->conn_list, conn, next); pthread_mutex_unlock(&vsocket->conn_mutex); return; err: free(conn); close(fd); } /* call back when there is new vhost-user connection from client */ static void vhost_user_server_new_connection(int fd, void *dat, int *remove __rte_unused) { struct vhost_user_socket *vsocket = dat; fd = accept(fd, NULL, NULL); if (fd < 0) return; RTE_LOG(INFO, VHOST_CONFIG, "new vhost user connection is %d\n", fd); vhost_user_add_connection(fd, vsocket); } static void vhost_user_read_cb(int connfd, void *dat, int *remove) { struct vhost_user_connection *conn = dat; struct vhost_user_socket *vsocket = conn->vsocket; int ret; ret = vhost_user_msg_handler(conn->vid, connfd); if (ret < 0) { close(connfd); *remove = 1; vhost_destroy_device(conn->vid); if (vsocket->notify_ops->destroy_connection) vsocket->notify_ops->destroy_connection(conn->vid); pthread_mutex_lock(&vsocket->conn_mutex); TAILQ_REMOVE(&vsocket->conn_list, conn, next); pthread_mutex_unlock(&vsocket->conn_mutex); free(conn); if (vsocket->reconnect) { create_unix_socket(vsocket); vhost_user_start_client(vsocket); } } } static int create_unix_socket(struct vhost_user_socket *vsocket) { int fd; struct sockaddr_un *un = &vsocket->un; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; RTE_LOG(INFO, VHOST_CONFIG, "vhost-user %s: socket created, fd: %d\n", vsocket->is_server ? "server" : "client", fd); if (!vsocket->is_server && fcntl(fd, F_SETFL, O_NONBLOCK)) { RTE_LOG(ERR, VHOST_CONFIG, "vhost-user: can't set nonblocking mode for socket, fd: " "%d (%s)\n", fd, strerror(errno)); close(fd); return -1; } memset(un, 0, sizeof(*un)); un->sun_family = AF_UNIX; strncpy(un->sun_path, vsocket->path, sizeof(un->sun_path)); un->sun_path[sizeof(un->sun_path) - 1] = '\0'; vsocket->socket_fd = fd; return 0; } static int vhost_user_start_server(struct vhost_user_socket *vsocket) { int ret; int fd = vsocket->socket_fd; const char *path = vsocket->path; ret = bind(fd, (struct sockaddr *)&vsocket->un, sizeof(vsocket->un)); if (ret < 0) { RTE_LOG(ERR, VHOST_CONFIG, "failed to bind to %s: %s; remove it and try again\n", path, strerror(errno)); goto err; } RTE_LOG(INFO, VHOST_CONFIG, "bind to %s\n", path); ret = listen(fd, MAX_VIRTIO_BACKLOG); if (ret < 0) goto err; ret = fdset_add(&vhost_user.fdset, fd, vhost_user_server_new_connection, NULL, vsocket); if (ret < 0) { RTE_LOG(ERR, VHOST_CONFIG, "failed to add listen fd %d to vhost server fdset\n", fd); goto err; } return 0; err: close(fd); return -1; } struct vhost_user_reconnect { struct sockaddr_un un; int fd; struct vhost_user_socket *vsocket; TAILQ_ENTRY(vhost_user_reconnect) next; }; TAILQ_HEAD(vhost_user_reconnect_tailq_list, vhost_user_reconnect); struct vhost_user_reconnect_list { struct vhost_user_reconnect_tailq_list head; pthread_mutex_t mutex; }; static struct vhost_user_reconnect_list reconn_list; static pthread_t reconn_tid; static int vhost_user_connect_nonblock(int fd, struct sockaddr *un, size_t sz) { int ret, flags; ret = connect(fd, un, sz); if (ret < 0 && errno != EISCONN) return -1; flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { RTE_LOG(ERR, VHOST_CONFIG, "can't get flags for connfd %d\n", fd); return -2; } if ((flags & O_NONBLOCK) && fcntl(fd, F_SETFL, flags & ~O_NONBLOCK)) { RTE_LOG(ERR, VHOST_CONFIG, "can't disable nonblocking on fd %d\n", fd); return -2; } return 0; } static void * vhost_user_client_reconnect(void *arg __rte_unused) { int ret; struct vhost_user_reconnect *reconn, *next; while (1) { pthread_mutex_lock(&reconn_list.mutex); /* * An equal implementation of TAILQ_FOREACH_SAFE, * which does not exist on all platforms. */ for (reconn = TAILQ_FIRST(&reconn_list.head); reconn != NULL; reconn = next) { next = TAILQ_NEXT(reconn, next); ret = vhost_user_connect_nonblock(reconn->fd, (struct sockaddr *)&reconn->un, sizeof(reconn->un)); if (ret == -2) { close(reconn->fd); RTE_LOG(ERR, VHOST_CONFIG, "reconnection for fd %d failed\n", reconn->fd); goto remove_fd; } if (ret == -1) continue; RTE_LOG(INFO, VHOST_CONFIG, "%s: connected\n", reconn->vsocket->path); vhost_user_add_connection(reconn->fd, reconn->vsocket); remove_fd: TAILQ_REMOVE(&reconn_list.head, reconn, next); free(reconn); } pthread_mutex_unlock(&reconn_list.mutex); sleep(1); } return NULL; } static int vhost_user_reconnect_init(void) { int ret; pthread_mutex_init(&reconn_list.mutex, NULL); TAILQ_INIT(&reconn_list.head); ret = pthread_create(&reconn_tid, NULL, vhost_user_client_reconnect, NULL); if (ret < 0) RTE_LOG(ERR, VHOST_CONFIG, "failed to create reconnect thread"); return ret; } static int vhost_user_start_client(struct vhost_user_socket *vsocket) { int ret; int fd = vsocket->socket_fd; const char *path = vsocket->path; struct vhost_user_reconnect *reconn; ret = vhost_user_connect_nonblock(fd, (struct sockaddr *)&vsocket->un, sizeof(vsocket->un)); if (ret == 0) { vhost_user_add_connection(fd, vsocket); return 0; } RTE_LOG(WARNING, VHOST_CONFIG, "failed to connect to %s: %s\n", path, strerror(errno)); if (ret == -2 || !vsocket->reconnect) { close(fd); return -1; } RTE_LOG(INFO, VHOST_CONFIG, "%s: reconnecting...\n", path); reconn = malloc(sizeof(*reconn)); if (reconn == NULL) { RTE_LOG(ERR, VHOST_CONFIG, "failed to allocate memory for reconnect\n"); close(fd); return -1; } reconn->un = vsocket->un; reconn->fd = fd; reconn->vsocket = vsocket; pthread_mutex_lock(&reconn_list.mutex); TAILQ_INSERT_TAIL(&reconn_list.head, reconn, next); pthread_mutex_unlock(&reconn_list.mutex); return 0; } static struct vhost_user_socket * find_vhost_user_socket(const char *path) { int i; for (i = 0; i < vhost_user.vsocket_cnt; i++) { struct vhost_user_socket *vsocket = vhost_user.vsockets[i]; if (!strcmp(vsocket->path, path)) return vsocket; } return NULL; } int rte_vhost_driver_disable_features(const char *path, uint64_t features) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); if (vsocket) vsocket->features &= ~features; pthread_mutex_unlock(&vhost_user.mutex); return vsocket ? 0 : -1; } int rte_vhost_driver_enable_features(const char *path, uint64_t features) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); if (vsocket) { if ((vsocket->supported_features & features) != features) { /* * trying to enable features the driver doesn't * support. */ pthread_mutex_unlock(&vhost_user.mutex); return -1; } vsocket->features |= features; } pthread_mutex_unlock(&vhost_user.mutex); return vsocket ? 0 : -1; } int rte_vhost_driver_set_features(const char *path, uint64_t features) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); if (vsocket) { vsocket->supported_features = features; vsocket->features = features; } pthread_mutex_unlock(&vhost_user.mutex); return vsocket ? 0 : -1; } int rte_vhost_driver_get_features(const char *path, uint64_t *features) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); if (vsocket) *features = vsocket->features; pthread_mutex_unlock(&vhost_user.mutex); if (!vsocket) { RTE_LOG(ERR, VHOST_CONFIG, "socket file %s is not registered yet.\n", path); return -1; } else { return 0; } } /* * Register a new vhost-user socket; here we could act as server * (the default case), or client (when RTE_VHOST_USER_CLIENT) flag * is set. */ int rte_vhost_driver_register(const char *path, uint64_t flags) { int ret = -1; struct vhost_user_socket *vsocket; if (!path) return -1; pthread_mutex_lock(&vhost_user.mutex); if (vhost_user.vsocket_cnt == MAX_VHOST_SOCKET) { RTE_LOG(ERR, VHOST_CONFIG, "error: the number of vhost sockets reaches maximum\n"); goto out; } vsocket = malloc(sizeof(struct vhost_user_socket)); if (!vsocket) goto out; memset(vsocket, 0, sizeof(struct vhost_user_socket)); vsocket->path = strdup(path); if (!vsocket->path) { free(vsocket); goto out; } TAILQ_INIT(&vsocket->conn_list); pthread_mutex_init(&vsocket->conn_mutex, NULL); vsocket->dequeue_zero_copy = flags & RTE_VHOST_USER_DEQUEUE_ZERO_COPY; /* * Set the supported features correctly for the builtin vhost-user * net driver. * * Applications know nothing about features the builtin virtio net * driver (virtio_net.c) supports, thus it's not possible for them * to invoke rte_vhost_driver_set_features(). To workaround it, here * we set it unconditionally. If the application want to implement * another vhost-user driver (say SCSI), it should call the * rte_vhost_driver_set_features(), which will overwrite following * two values. */ vsocket->supported_features = VIRTIO_NET_SUPPORTED_FEATURES; vsocket->features = VIRTIO_NET_SUPPORTED_FEATURES; if ((flags & RTE_VHOST_USER_CLIENT) != 0) { vsocket->reconnect = !(flags & RTE_VHOST_USER_NO_RECONNECT); if (vsocket->reconnect && reconn_tid == 0) { if (vhost_user_reconnect_init() < 0) { free(vsocket->path); free(vsocket); goto out; } } } else { vsocket->is_server = true; } ret = create_unix_socket(vsocket); if (ret < 0) { free(vsocket->path); free(vsocket); goto out; } vhost_user.vsockets[vhost_user.vsocket_cnt++] = vsocket; out: pthread_mutex_unlock(&vhost_user.mutex); return ret; } static bool vhost_user_remove_reconnect(struct vhost_user_socket *vsocket) { int found = false; struct vhost_user_reconnect *reconn, *next; pthread_mutex_lock(&reconn_list.mutex); for (reconn = TAILQ_FIRST(&reconn_list.head); reconn != NULL; reconn = next) { next = TAILQ_NEXT(reconn, next); if (reconn->vsocket == vsocket) { TAILQ_REMOVE(&reconn_list.head, reconn, next); close(reconn->fd); free(reconn); found = true; break; } } pthread_mutex_unlock(&reconn_list.mutex); return found; } /** * Unregister the specified vhost socket */ int rte_vhost_driver_unregister(const char *path) { int i; int count; struct vhost_user_connection *conn; pthread_mutex_lock(&vhost_user.mutex); for (i = 0; i < vhost_user.vsocket_cnt; i++) { struct vhost_user_socket *vsocket = vhost_user.vsockets[i]; if (!strcmp(vsocket->path, path)) { if (vsocket->is_server) { fdset_del(&vhost_user.fdset, vsocket->socket_fd); close(vsocket->socket_fd); unlink(path); } else if (vsocket->reconnect) { vhost_user_remove_reconnect(vsocket); } pthread_mutex_lock(&vsocket->conn_mutex); TAILQ_FOREACH(conn, &vsocket->conn_list, next) { close(conn->connfd); } pthread_mutex_unlock(&vsocket->conn_mutex); do { pthread_mutex_lock(&vsocket->conn_mutex); conn = TAILQ_FIRST(&vsocket->conn_list); pthread_mutex_unlock(&vsocket->conn_mutex); } while (conn != NULL); free(vsocket->path); free(vsocket); count = --vhost_user.vsocket_cnt; vhost_user.vsockets[i] = vhost_user.vsockets[count]; vhost_user.vsockets[count] = NULL; pthread_mutex_unlock(&vhost_user.mutex); return 0; } } pthread_mutex_unlock(&vhost_user.mutex); return -1; } /* * Register ops so that we can add/remove device to data core. */ int rte_vhost_driver_callback_register(const char *path, struct vhost_device_ops const * const ops) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); if (vsocket) vsocket->notify_ops = ops; pthread_mutex_unlock(&vhost_user.mutex); return vsocket ? 0 : -1; } struct vhost_device_ops const * vhost_driver_callback_get(const char *path) { struct vhost_user_socket *vsocket; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); pthread_mutex_unlock(&vhost_user.mutex); return vsocket ? vsocket->notify_ops : NULL; } int rte_vhost_driver_start(const char *path) { struct vhost_user_socket *vsocket; static pthread_t fdset_tid; pthread_mutex_lock(&vhost_user.mutex); vsocket = find_vhost_user_socket(path); pthread_mutex_unlock(&vhost_user.mutex); if (!vsocket) return -1; if (fdset_tid == 0) { int ret = pthread_create(&fdset_tid, NULL, fdset_event_dispatch, &vhost_user.fdset); if (ret < 0) RTE_LOG(ERR, VHOST_CONFIG, "failed to create fdset handling thread"); } if (vsocket->is_server) return vhost_user_start_server(vsocket); else return vhost_user_start_client(vsocket); }