/*
- * Copyright (C) 2016-2019 Red Hat, Inc.
+ * Copyright Red Hat
* Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws>
*
* Network Block Device Client Side
uint32_t len, const char *data,
Error **errp)
{
+ ERRP_GUARD();
NBDOption req;
QEMU_BUILD_BUG_ON(sizeof(req) != 16);
static int nbd_handle_reply_err(QIOChannel *ioc, NBDOptionReply *reply,
bool strict, Error **errp)
{
+ ERRP_GUARD();
g_autofree char *msg = NULL;
if (!(reply->type & (1 << 31))) {
return -1;
}
len -= sizeof(namelen);
- if (len < namelen) {
- error_setg(errp, "incorrect option name length");
+ if (len < namelen || namelen > NBD_MAX_STRING_SIZE) {
+ error_setg(errp, "incorrect name length in server's list response");
nbd_send_opt_abort(ioc);
return -1;
}
local_name[namelen] = '\0';
len -= namelen;
if (len) {
+ if (len > NBD_MAX_STRING_SIZE) {
+ error_setg(errp, "incorrect description length in server's "
+ "list response");
+ nbd_send_opt_abort(ioc);
+ return -1;
+ }
local_desc = g_malloc(len + 1);
if (nbd_read(ioc, local_desc, len, "export description", errp) < 0) {
nbd_send_opt_abort(ioc);
static int nbd_opt_info_or_go(QIOChannel *ioc, uint32_t opt,
NBDExportInfo *info, Error **errp)
{
+ ERRP_GUARD();
NBDOptionReply reply;
uint32_t len = strlen(info->name);
uint16_t type;
break;
default:
+ /*
+ * Not worth the bother to check if NBD_INFO_NAME or
+ * NBD_INFO_DESCRIPTION exceed NBD_MAX_STRING_SIZE.
+ */
trace_nbd_opt_info_unknown(type, nbd_info_lookup(type));
if (nbd_drop(ioc, len, errp) < 0) {
error_prepend(errp, "Failed to read info payload: ");
Error **errp)
{
int ret;
- uint32_t export_len = strlen(export);
+ uint32_t export_len;
uint32_t queries = !!query;
uint32_t query_len = 0;
uint32_t data_len;
char *data;
char *p;
+ assert(strnlen(export, NBD_MAX_STRING_SIZE + 1) <= NBD_MAX_STRING_SIZE);
+ export_len = strlen(export);
data_len = sizeof(export_len) + export_len + sizeof(queries);
if (query) {
+ assert(strnlen(query, NBD_MAX_STRING_SIZE + 1) <= NBD_MAX_STRING_SIZE);
query_len = strlen(query);
data_len += sizeof(query_len) + query_len;
} else {
* Start the handshake to the server. After a positive return, the server
* is ready to accept additional NBD_OPT requests.
* Returns: negative errno: failure talking to server
- * 0: server is oldstyle, must call nbd_negotiate_finish_oldstyle
- * 1: server is newstyle, but can only accept EXPORT_NAME
- * 2: server is newstyle, but lacks structured replies
- * 3: server is newstyle and set up for structured replies
+ * non-negative: enum NBDMode describing server abilities
*/
-static int nbd_start_negotiate(AioContext *aio_context, QIOChannel *ioc,
- QCryptoTLSCreds *tlscreds,
+static int nbd_start_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, QIOChannel **outioc,
- bool structured_reply, bool *zeroes,
+ NBDMode max_mode, bool *zeroes,
Error **errp)
{
+ ERRP_GUARD();
uint64_t magic;
trace_nbd_start_negotiate(tlscreds, hostname ? hostname : "<null>");
return -EINVAL;
}
ioc = *outioc;
- if (aio_context) {
- qio_channel_set_blocking(ioc, false, NULL);
- qio_channel_attach_aio_context(ioc, aio_context);
- }
} else {
error_setg(errp, "Server does not support STARTTLS");
return -EINVAL;
if (fixedNewStyle) {
int result = 0;
- if (structured_reply) {
+ if (max_mode >= NBD_MODE_EXTENDED) {
+ result = nbd_request_simple_option(ioc,
+ NBD_OPT_EXTENDED_HEADERS,
+ false, errp);
+ if (result) {
+ return result < 0 ? -EINVAL : NBD_MODE_EXTENDED;
+ }
+ }
+ if (max_mode >= NBD_MODE_STRUCTURED) {
result = nbd_request_simple_option(ioc,
NBD_OPT_STRUCTURED_REPLY,
false, errp);
- if (result < 0) {
- return -EINVAL;
+ if (result) {
+ return result < 0 ? -EINVAL : NBD_MODE_STRUCTURED;
}
}
- return 2 + result;
+ return NBD_MODE_SIMPLE;
} else {
- return 1;
+ return NBD_MODE_EXPORT_NAME;
}
} else if (magic == NBD_CLIENT_MAGIC) {
if (tlscreds) {
error_setg(errp, "Server does not support STARTTLS");
return -EINVAL;
}
- return 0;
+ return NBD_MODE_OLDSTYLE;
} else {
error_setg(errp, "Bad server magic received: 0x%" PRIx64, magic);
return -EINVAL;
* Returns: negative errno: failure talking to server
* 0: server is connected
*/
-int nbd_receive_negotiate(AioContext *aio_context, QIOChannel *ioc,
- QCryptoTLSCreds *tlscreds,
+int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, QIOChannel **outioc,
NBDExportInfo *info, Error **errp)
{
+ ERRP_GUARD();
int result;
bool zeroes;
bool base_allocation = info->base_allocation;
- assert(info->name);
+ assert(info->name && strlen(info->name) <= NBD_MAX_STRING_SIZE);
trace_nbd_receive_negotiate_name(info->name);
- result = nbd_start_negotiate(aio_context, ioc, tlscreds, hostname, outioc,
- info->structured_reply, &zeroes, errp);
+ result = nbd_start_negotiate(ioc, tlscreds, hostname, outioc,
+ info->mode, &zeroes, errp);
+ if (result < 0) {
+ return result;
+ }
- info->structured_reply = false;
+ info->mode = result;
info->base_allocation = false;
if (tlscreds && *outioc) {
ioc = *outioc;
}
- switch (result) {
- case 3: /* newstyle, with structured replies */
- info->structured_reply = true;
+ switch (info->mode) {
+ case NBD_MODE_EXTENDED:
+ case NBD_MODE_STRUCTURED:
if (base_allocation) {
result = nbd_negotiate_simple_meta_context(ioc, info, errp);
if (result < 0) {
info->base_allocation = result == 1;
}
/* fall through */
- case 2: /* newstyle, try OPT_GO */
+ case NBD_MODE_SIMPLE:
/* Try NBD_OPT_GO first - if it works, we are done (it
* also gives us a good message if the server requires
* TLS). If it is not available, fall back to
return -EINVAL;
}
/* fall through */
- case 1: /* newstyle, but limited to EXPORT_NAME */
+ case NBD_MODE_EXPORT_NAME:
/* write the export name request */
if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, info->name,
errp) < 0) {
return -EINVAL;
}
break;
- case 0: /* oldstyle, parse length and flags */
+ case NBD_MODE_OLDSTYLE:
if (*info->name) {
error_setg(errp, "Server does not support non-empty export names");
return -EINVAL;
}
break;
default:
- return result;
+ g_assert_not_reached();
}
trace_nbd_receive_negotiate_size_flags(info->size, info->flags);
QIOChannel *sioc = NULL;
*info = NULL;
- result = nbd_start_negotiate(NULL, ioc, tlscreds, hostname, &sioc, true,
- NULL, errp);
+ result = nbd_start_negotiate(ioc, tlscreds, hostname, &sioc,
+ NBD_MODE_EXTENDED, NULL, errp);
if (tlscreds && sioc) {
ioc = sioc;
}
+ if (result < 0) {
+ goto out;
+ }
- switch (result) {
- case 2:
- case 3:
+ switch ((NBDMode)result) {
+ case NBD_MODE_SIMPLE:
+ case NBD_MODE_STRUCTURED:
+ case NBD_MODE_EXTENDED:
/* newstyle - use NBD_OPT_LIST to populate array, then try
* NBD_OPT_INFO on each array member. If structured replies
* are enabled, also try NBD_OPT_LIST_META_CONTEXT. */
memset(&array[count - 1], 0, sizeof(*array));
array[count - 1].name = name;
array[count - 1].description = desc;
- array[count - 1].structured_reply = result == 3;
+ array[count - 1].mode = result;
}
for (i = 0; i < count; i++) {
break;
}
- if (result == 3 &&
+ if (result >= NBD_MODE_STRUCTURED &&
nbd_list_meta_contexts(ioc, &array[i], errp) < 0) {
goto out;
}
/* Send NBD_OPT_ABORT as a courtesy before hanging up */
nbd_send_opt_abort(ioc);
break;
- case 1: /* newstyle, but limited to EXPORT_NAME */
+ case NBD_MODE_EXPORT_NAME:
error_setg(errp, "Server does not support export lists");
/* We can't even send NBD_OPT_ABORT, so merely hang up */
goto out;
- case 0: /* oldstyle, parse length and flags */
+ case NBD_MODE_OLDSTYLE:
+ /* Lone export name is implied, but we can parse length and flags */
array = g_new0(NBDExportInfo, 1);
array->name = g_strdup("");
+ array->mode = NBD_MODE_OLDSTYLE;
count = 1;
if (nbd_negotiate_finish_oldstyle(ioc, array, errp) < 0) {
/* Send NBD_CMD_DISC as a courtesy to the server, but ignore all
* errors now that we have the information we wanted. */
if (nbd_drop(ioc, 124, NULL) == 0) {
- NBDRequest request = { .type = NBD_CMD_DISC };
+ NBDRequest request = { .type = NBD_CMD_DISC, .mode = result };
nbd_send_request(ioc, &request);
}
break;
default:
- goto out;
+ g_assert_not_reached();
}
*info = array;
int nbd_send_request(QIOChannel *ioc, NBDRequest *request)
{
- uint8_t buf[NBD_REQUEST_SIZE];
+ uint8_t buf[NBD_EXTENDED_REQUEST_SIZE];
+ size_t len;
- trace_nbd_send_request(request->from, request->len, request->handle,
+ trace_nbd_send_request(request->from, request->len, request->cookie,
request->flags, request->type,
nbd_cmd_lookup(request->type));
- stl_be_p(buf, NBD_REQUEST_MAGIC);
stw_be_p(buf + 4, request->flags);
stw_be_p(buf + 6, request->type);
- stq_be_p(buf + 8, request->handle);
+ stq_be_p(buf + 8, request->cookie);
stq_be_p(buf + 16, request->from);
- stl_be_p(buf + 24, request->len);
+ if (request->mode >= NBD_MODE_EXTENDED) {
+ stl_be_p(buf, NBD_EXTENDED_REQUEST_MAGIC);
+ stq_be_p(buf + 24, request->len);
+ len = NBD_EXTENDED_REQUEST_SIZE;
+ } else {
+ assert(request->len <= UINT32_MAX);
+ stl_be_p(buf, NBD_REQUEST_MAGIC);
+ stl_be_p(buf + 24, request->len);
+ len = NBD_REQUEST_SIZE;
+ }
- return nbd_write(ioc, buf, sizeof(buf), NULL);
+ return nbd_write(ioc, buf, len, NULL);
}
/* nbd_receive_simple_reply
}
reply->error = be32_to_cpu(reply->error);
- reply->handle = be64_to_cpu(reply->handle);
+ reply->cookie = be64_to_cpu(reply->cookie);
return 0;
}
-/* nbd_receive_structured_reply_chunk
+/* nbd_receive_reply_chunk_header
* Read structured reply chunk except magic field (which should be already
- * read).
+ * read). Normalize into the compact form.
* Payload is not read.
*/
-static int nbd_receive_structured_reply_chunk(QIOChannel *ioc,
- NBDStructuredReplyChunk *chunk,
- Error **errp)
+static int nbd_receive_reply_chunk_header(QIOChannel *ioc, NBDReply *chunk,
+ Error **errp)
{
int ret;
+ size_t len;
+ uint64_t payload_len;
- assert(chunk->magic == NBD_STRUCTURED_REPLY_MAGIC);
+ if (chunk->magic == NBD_STRUCTURED_REPLY_MAGIC) {
+ len = sizeof(chunk->structured);
+ } else {
+ assert(chunk->magic == NBD_EXTENDED_REPLY_MAGIC);
+ len = sizeof(chunk->extended);
+ }
ret = nbd_read(ioc, (uint8_t *)chunk + sizeof(chunk->magic),
- sizeof(*chunk) - sizeof(chunk->magic), "structured chunk",
+ len - sizeof(chunk->magic), "structured chunk",
errp);
if (ret < 0) {
return ret;
}
- chunk->flags = be16_to_cpu(chunk->flags);
- chunk->type = be16_to_cpu(chunk->type);
- chunk->handle = be64_to_cpu(chunk->handle);
- chunk->length = be32_to_cpu(chunk->length);
+ /* flags, type, and cookie occupy same space between forms */
+ chunk->structured.flags = be16_to_cpu(chunk->structured.flags);
+ chunk->structured.type = be16_to_cpu(chunk->structured.type);
+ chunk->structured.cookie = be64_to_cpu(chunk->structured.cookie);
+
+ /*
+ * Because we use BLOCK_STATUS with REQ_ONE, and cap READ requests
+ * at 32M, no valid server should send us payload larger than
+ * this. Even if we stopped using REQ_ONE, sane servers will cap
+ * the number of extents they return for block status.
+ */
+ if (chunk->magic == NBD_STRUCTURED_REPLY_MAGIC) {
+ payload_len = be32_to_cpu(chunk->structured.length);
+ } else {
+ /* For now, we are ignoring the extended header offset. */
+ payload_len = be64_to_cpu(chunk->extended.length);
+ chunk->magic = NBD_STRUCTURED_REPLY_MAGIC;
+ }
+ if (payload_len > NBD_MAX_BUFFER_SIZE + sizeof(NBDStructuredReadData)) {
+ error_setg(errp, "server chunk %" PRIu32 " (%s) payload is too long",
+ chunk->structured.type,
+ nbd_rep_lookup(chunk->structured.type));
+ return -EINVAL;
+ }
+ chunk->structured.length = payload_len;
return 0;
}
len = qio_channel_readv(ioc, &iov, 1, errp);
if (len == QIO_CHANNEL_ERR_BLOCK) {
- bdrv_dec_in_flight(bs);
qio_channel_yield(ioc, G_IO_IN);
- bdrv_inc_in_flight(bs);
continue;
} else if (len < 0) {
return -EIO;
/* nbd_receive_reply
*
- * Decreases bs->in_flight while waiting for a new reply. This yield is where
- * we wait indefinitely and the coroutine must be able to be safely reentered
- * for nbd_client_attach_aio_context().
+ * Wait for a new reply. If this yields, the coroutine must be able to be
+ * safely reentered for nbd_client_attach_aio_context(). @mode determines
+ * which reply magic we are expecting, although this normalizes the result
+ * so that the caller only has to work with compact headers.
*
* Returns 1 on success
- * 0 on eof, when no data was read (errp is not set)
- * negative errno on failure (errp is set)
+ * 0 on eof, when no data was read
+ * negative errno on failure
*/
int coroutine_fn nbd_receive_reply(BlockDriverState *bs, QIOChannel *ioc,
- NBDReply *reply, Error **errp)
+ NBDReply *reply, NBDMode mode, Error **errp)
{
int ret;
const char *type;
+ uint32_t expected;
ret = nbd_read_eof(bs, ioc, &reply->magic, sizeof(reply->magic), errp);
if (ret <= 0) {
reply->magic = be32_to_cpu(reply->magic);
+ /* Diagnose but accept wrong-width header */
switch (reply->magic) {
case NBD_SIMPLE_REPLY_MAGIC:
+ if (mode >= NBD_MODE_EXTENDED) {
+ trace_nbd_receive_wrong_header(reply->magic,
+ nbd_mode_lookup(mode));
+ }
ret = nbd_receive_simple_reply(ioc, &reply->simple, errp);
if (ret < 0) {
- break;
+ return ret;
}
trace_nbd_receive_simple_reply(reply->simple.error,
nbd_err_lookup(reply->simple.error),
- reply->handle);
+ reply->cookie);
break;
case NBD_STRUCTURED_REPLY_MAGIC:
- ret = nbd_receive_structured_reply_chunk(ioc, &reply->structured, errp);
+ case NBD_EXTENDED_REPLY_MAGIC:
+ expected = mode >= NBD_MODE_EXTENDED ? NBD_EXTENDED_REPLY_MAGIC
+ : NBD_STRUCTURED_REPLY_MAGIC;
+ if (reply->magic != expected) {
+ trace_nbd_receive_wrong_header(reply->magic,
+ nbd_mode_lookup(mode));
+ }
+ ret = nbd_receive_reply_chunk_header(ioc, reply, errp);
if (ret < 0) {
- break;
+ return ret;
}
type = nbd_reply_type_lookup(reply->structured.type);
- trace_nbd_receive_structured_reply_chunk(reply->structured.flags,
- reply->structured.type, type,
- reply->structured.handle,
- reply->structured.length);
+ trace_nbd_receive_reply_chunk_header(reply->structured.flags,
+ reply->structured.type, type,
+ reply->structured.cookie,
+ reply->structured.length);
break;
default:
+ trace_nbd_receive_wrong_header(reply->magic, nbd_mode_lookup(mode));
error_setg(errp, "invalid magic (got 0x%" PRIx32 ")", reply->magic);
return -EINVAL;
}
- if (ret < 0) {
- return ret;
- }
return 1;
}