diff options
Diffstat (limited to 'lib/tran.c')
-rw-r--r-- | lib/tran.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/lib/tran.c b/lib/tran.c new file mode 100644 index 0000000..ba49fd6 --- /dev/null +++ b/lib/tran.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2020 Nutanix Inc. All rights reserved. + * + * Authors: Thanos Makatos <thanos@nutanix.com> + * Swapnil Ingle <swapnil.ingle@nutanix.com> + * Felipe Franciosi <felipe@nutanix.com> + * + * 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 Nutanix 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 <COPYRIGHT HOLDER> 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 <sys/param.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <json.h> + +#include "libvfio-user.h" +#include "migration.h" +#include "tran.h" + +// FIXME: is this the value we want? +#define SERVER_MAX_FDS 8 + +/* + * Expected JSON is of the form: + * + * { + * "capabilities": { + * "max_msg_fds": 32, + * "max_data_xfer_size": 1048576 + * "migration": { + * "pgsize": 4096 + * } + * } + * } + * + * with everything being optional. Note that json_object_get_uint64() is only + * available in newer library versions, so we don't use it. + */ +int +tran_parse_version_json(const char *json_str, int *client_max_fdsp, + size_t *client_max_data_xfer_sizep, size_t *pgsizep) +{ + struct json_object *jo_caps = NULL; + struct json_object *jo_top = NULL; + struct json_object *jo = NULL; + int ret = EINVAL; + + if ((jo_top = json_tokener_parse(json_str)) == NULL) { + goto out; + } + + if (!json_object_object_get_ex(jo_top, "capabilities", &jo_caps)) { + ret = 0; + goto out; + } + + if (json_object_get_type(jo_caps) != json_type_object) { + goto out; + } + + if (json_object_object_get_ex(jo_caps, "max_msg_fds", &jo)) { + if (json_object_get_type(jo) != json_type_int) { + goto out; + } + + errno = 0; + *client_max_fdsp = (int)json_object_get_int64(jo); + + if (errno != 0) { + goto out; + } + } + + if (json_object_object_get_ex(jo_caps, "max_data_xfer_size", &jo)) { + if (json_object_get_type(jo) != json_type_int) { + goto out; + } + + errno = 0; + *client_max_data_xfer_sizep = (int)json_object_get_int64(jo); + + if (errno != 0) { + goto out; + } + } + if (json_object_object_get_ex(jo_caps, "migration", &jo)) { + struct json_object *jo2 = NULL; + + if (json_object_get_type(jo) != json_type_object) { + goto out; + } + + if (json_object_object_get_ex(jo, "pgsize", &jo2)) { + if (json_object_get_type(jo2) != json_type_int) { + goto out; + } + + errno = 0; + *pgsizep = (size_t)json_object_get_int64(jo2); + + if (errno != 0) { + goto out; + } + } + } + + ret = 0; + +out: + /* We just need to put our top-level object. */ + json_object_put(jo_top); + if (ret != 0) { + return ERROR_INT(ret); + } + return 0; +} + +static int +recv_version(vfu_ctx_t *vfu_ctx, uint16_t *msg_idp, + struct vfio_user_version **versionp) +{ + struct vfio_user_version *cversion = NULL; + vfu_msg_t msg = { { 0 } }; + int ret; + + *versionp = NULL; + + ret = vfu_ctx->tran->recv_msg(vfu_ctx, &msg); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to receive version: %m"); + return ret; + } + + *msg_idp = msg.hdr.msg_id; + + if (msg.hdr.cmd != VFIO_USER_VERSION) { + vfu_log(vfu_ctx, LOG_ERR, "msg%#hx: invalid cmd %hu (expected %u)", + *msg_idp, msg.hdr.cmd, VFIO_USER_VERSION); + ret = EINVAL; + goto out; + } + + if (msg.in.nr_fds != 0) { + vfu_log(vfu_ctx, LOG_ERR, + "msg%#hx: VFIO_USER_VERSION: sent with %zu fds", *msg_idp, + msg.in.nr_fds); + ret = EINVAL; + goto out; + } + + if (msg.in.iov.iov_len < sizeof(*cversion)) { + vfu_log(vfu_ctx, LOG_ERR, + "msg%#hx: VFIO_USER_VERSION: invalid size %lu", + *msg_idp, msg.in.iov.iov_len); + ret = EINVAL; + goto out; + } + + cversion = msg.in.iov.iov_base; + + if (cversion->major != LIB_VFIO_USER_MAJOR) { + vfu_log(vfu_ctx, LOG_ERR, "unsupported client major %hu (must be %u)", + cversion->major, LIB_VFIO_USER_MAJOR); + ret = EINVAL; + goto out; + } + + vfu_ctx->client_max_fds = 1; + vfu_ctx->client_max_data_xfer_size = VFIO_USER_DEFAULT_MAX_DATA_XFER_SIZE; + + if (msg.in.iov.iov_len > sizeof(*cversion)) { + const char *json_str = (const char *)cversion->data; + size_t len = msg.in.iov.iov_len - sizeof(*cversion); + size_t pgsize = 0; + + if (json_str[len - 1] != '\0') { + vfu_log(vfu_ctx, LOG_ERR, "ignoring invalid JSON from client"); + ret = EINVAL; + goto out; + } + + ret = tran_parse_version_json(json_str, &vfu_ctx->client_max_fds, + &vfu_ctx->client_max_data_xfer_size, + &pgsize); + + if (ret < 0) { + /* No client-supplied strings in the log for release build. */ +#ifdef DEBUG + vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON \"%s\"", + json_str); +#else + vfu_log(vfu_ctx, LOG_ERR, "failed to parse client JSON"); +#endif + ret = errno; + goto out; + } + + if (vfu_ctx->migration != NULL && pgsize != 0) { + ret = migration_set_pgsize(vfu_ctx->migration, pgsize); + + if (ret != 0) { + vfu_log(vfu_ctx, LOG_ERR, "refusing client page size of %zu", + pgsize); + ret = errno; + goto out; + } + } + + // FIXME: is the code resilient against ->client_max_fds == 0? + if (vfu_ctx->client_max_fds < 0 || + vfu_ctx->client_max_fds > VFIO_USER_CLIENT_MAX_MSG_FDS_LIMIT) { + vfu_log(vfu_ctx, LOG_ERR, "refusing client max_msg_fds of %d", + vfu_ctx->client_max_fds); + ret = EINVAL; + goto out; + } + } + +out: + if (ret != 0) { + vfu_msg_t rmsg = { { 0 } }; + size_t i; + + rmsg.hdr = msg.hdr; + + (void) vfu_ctx->tran->reply(vfu_ctx, &rmsg, ret); + + for (i = 0; i < msg.in.nr_fds; i++) { + if (msg.in.fds[i] != -1) { + close(msg.in.fds[i]); + } + } + + free(msg.in.iov.iov_base); + + *versionp = NULL; + return ERROR_INT(ret); + } + + *versionp = cversion; + return 0; +} + +static int +send_version(vfu_ctx_t *vfu_ctx, uint16_t msg_id, + struct vfio_user_version *cversion) +{ + struct vfio_user_version sversion = { 0 }; + struct iovec iovecs[2] = { { 0 } }; + char server_caps[1024]; + vfu_msg_t msg = { { 0 } }; + int slen; + + if (vfu_ctx->migration == NULL) { + slen = snprintf(server_caps, sizeof(server_caps), + "{" + "\"capabilities\":{" + "\"max_msg_fds\":%u," + "\"max_data_xfer_size\":%u" + "}" + "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE); + } else { + slen = snprintf(server_caps, sizeof(server_caps), + "{" + "\"capabilities\":{" + "\"max_msg_fds\":%u," + "\"max_data_xfer_size\":%u," + "\"migration\":{" + "\"pgsize\":%zu" + "}" + "}" + "}", SERVER_MAX_FDS, SERVER_MAX_DATA_XFER_SIZE, + migration_get_pgsize(vfu_ctx->migration)); + } + + // FIXME: we should save the client minor here, and check that before trying + // to send unsupported things. + sversion.major = LIB_VFIO_USER_MAJOR; + sversion.minor = MIN(cversion->minor, LIB_VFIO_USER_MINOR); + + iovecs[0].iov_base = &sversion; + iovecs[0].iov_len = sizeof(sversion); + iovecs[1].iov_base = server_caps; + /* Include the NUL. */ + iovecs[1].iov_len = slen + 1; + + msg.hdr.cmd = VFIO_USER_VERSION; + msg.hdr.msg_id = msg_id; + msg.out_iovecs = iovecs; + msg.nr_out_iovecs = 2; + + return vfu_ctx->tran->reply(vfu_ctx, &msg, 0); +} + +int +tran_negotiate(vfu_ctx_t *vfu_ctx) +{ + struct vfio_user_version *client_version = NULL; + uint16_t msg_id = 0x0bad; + int ret; + + ret = recv_version(vfu_ctx, &msg_id, &client_version); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to recv version: %m"); + return ret; + } + + ret = send_version(vfu_ctx, msg_id, client_version); + + free(client_version); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to send version: %m"); + } + + return ret; +} + +/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ |