/* * Copyright (c) 2020 Nutanix Inc. All rights reserved. * * Authors: Thanos Makatos * Swapnil Ingle * Felipe Franciosi * * 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 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 "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: */