nbd: Implement NBD_OPT_GO on client

NBD_OPT_EXPORT_NAME is lousy: per the NBD protocol, any failure
requires the server to close the connection rather than report an
error to us.  Therefore, upstream NBD recently added NBD_OPT_GO as
the improved version of the option that does what we want [1]: it
reports sane errors on failures, and on success provides at least
as much info as NBD_OPT_EXPORT_NAME.

[1] https://github.com/NetworkBlockDevice/nbd/blob/extension-info/doc/proto.md

This is a first cut at use of the information types.  Note that we
do not need to use NBD_OPT_INFO, and that use of NBD_OPT_GO means
we no longer have to use NBD_OPT_LIST to learn whether a server
requires TLS (this requires servers that gracefully handle unknown
NBD_OPT, many servers prior to qemu 2.5 were buggy, but I have patched
qemu, upstream nbd, and nbdkit in the meantime, in part because of
interoperability testing with this patch).  We still fall back to
NBD_OPT_LIST when NBD_OPT_GO is not supported on the server, as it
is still one last chance for a nicer error message.  Later patches
will use further info, like NBD_INFO_BLOCK_SIZE.

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <20170707203049.534-8-eblake@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Eric Blake 2017-07-07 15:30:47 -05:00 committed by Paolo Bonzini
parent f37708f6b8
commit 8ecaeae822
3 changed files with 130 additions and 2 deletions

View File

@ -350,6 +350,114 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
} }
/* Returns -1 if NBD_OPT_GO proves the export @wantname cannot be
* used, 0 if NBD_OPT_GO is unsupported (fall back to NBD_OPT_LIST and
* NBD_OPT_EXPORT_NAME in that case), and > 0 if the export is good to
* go (with @info populated). */
static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
NBDExportInfo *info, Error **errp)
{
nbd_opt_reply reply;
uint32_t len = strlen(wantname);
uint16_t type;
int error;
char *buf;
/* The protocol requires that the server send NBD_INFO_EXPORT with
* a non-zero flags (at least NBD_FLAG_HAS_FLAGS must be set); so
* flags still 0 is a witness of a broken server. */
info->flags = 0;
trace_nbd_opt_go_start(wantname);
buf = g_malloc(4 + len + 2 + 1);
stl_be_p(buf, len);
memcpy(buf + 4, wantname, len);
/* No requests, live with whatever server sends */
stw_be_p(buf + 4 + len, 0);
if (nbd_send_option_request(ioc, NBD_OPT_GO, len + 6, buf, errp) < 0) {
return -1;
}
while (1) {
if (nbd_receive_option_reply(ioc, NBD_OPT_GO, &reply, errp) < 0) {
return -1;
}
error = nbd_handle_reply_err(ioc, &reply, errp);
if (error <= 0) {
return error;
}
len = reply.length;
if (reply.type == NBD_REP_ACK) {
/* Server is done sending info and moved into transmission
phase, but make sure it sent flags */
if (len) {
error_setg(errp, "server sent invalid NBD_REP_ACK");
nbd_send_opt_abort(ioc);
return -1;
}
if (!info->flags) {
error_setg(errp, "broken server omitted NBD_INFO_EXPORT");
nbd_send_opt_abort(ioc);
return -1;
}
trace_nbd_opt_go_success();
return 1;
}
if (reply.type != NBD_REP_INFO) {
error_setg(errp, "unexpected reply type %" PRIx32 ", expected %x",
reply.type, NBD_REP_INFO);
nbd_send_opt_abort(ioc);
return -1;
}
if (len < sizeof(type)) {
error_setg(errp, "NBD_REP_INFO length %" PRIu32 " is too short",
len);
nbd_send_opt_abort(ioc);
return -1;
}
if (nbd_read(ioc, &type, sizeof(type), errp) < 0) {
error_prepend(errp, "failed to read info type");
nbd_send_opt_abort(ioc);
return -1;
}
len -= sizeof(type);
be16_to_cpus(&type);
switch (type) {
case NBD_INFO_EXPORT:
if (len != sizeof(info->size) + sizeof(info->flags)) {
error_setg(errp, "remaining export info len %" PRIu32
" is unexpected size", len);
nbd_send_opt_abort(ioc);
return -1;
}
if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
error_prepend(errp, "failed to read info size");
nbd_send_opt_abort(ioc);
return -1;
}
be64_to_cpus(&info->size);
if (nbd_read(ioc, &info->flags, sizeof(info->flags), errp) < 0) {
error_prepend(errp, "failed to read info flags");
nbd_send_opt_abort(ioc);
return -1;
}
be16_to_cpus(&info->flags);
trace_nbd_receive_negotiate_size_flags(info->size, info->flags);
break;
default:
trace_nbd_opt_go_info_unknown(type, nbd_info_lookup(type));
if (nbd_drop(ioc, len, errp) < 0) {
error_prepend(errp, "Failed to read info payload");
nbd_send_opt_abort(ioc);
return -1;
}
break;
}
}
}
/* Return -1 on failure, 0 if wantname is an available export. */ /* Return -1 on failure, 0 if wantname is an available export. */
static int nbd_receive_query_exports(QIOChannel *ioc, static int nbd_receive_query_exports(QIOChannel *ioc,
const char *wantname, const char *wantname,
@ -531,11 +639,25 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
name = ""; name = "";
} }
if (fixedNewStyle) { if (fixedNewStyle) {
int result;
/* 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
* NBD_OPT_LIST for nicer error messages about a missing
* export, then use NBD_OPT_EXPORT_NAME. */
result = nbd_opt_go(ioc, name, info, errp);
if (result < 0) {
goto fail;
}
if (result > 0) {
return 0;
}
/* Check our desired export is present in the /* Check our desired export is present in the
* server export list. Since NBD_OPT_EXPORT_NAME * server export list. Since NBD_OPT_EXPORT_NAME
* cannot return an error message, running this * cannot return an error message, running this
* query gives us good error reporting if the * query gives us better error reporting if the
* server required TLS * export name is not available.
*/ */
if (nbd_receive_query_exports(ioc, name, errp) < 0) { if (nbd_receive_query_exports(ioc, name, errp) < 0) {
goto fail; goto fail;

View File

@ -37,8 +37,11 @@
* https://github.com/yoe/nbd/blob/master/doc/proto.md * https://github.com/yoe/nbd/blob/master/doc/proto.md
*/ */
/* Size of all NBD_OPT_*, without payload */
#define NBD_REQUEST_SIZE (4 + 2 + 2 + 8 + 8 + 4) #define NBD_REQUEST_SIZE (4 + 2 + 2 + 8 + 8 + 4)
/* Size of all NBD_REP_* sent in answer to most NBD_OPT_*, without payload */
#define NBD_REPLY_SIZE (4 + 4 + 8) #define NBD_REPLY_SIZE (4 + 4 + 8)
#define NBD_REQUEST_MAGIC 0x25609513 #define NBD_REQUEST_MAGIC 0x25609513
#define NBD_REPLY_MAGIC 0x67446698 #define NBD_REPLY_MAGIC 0x67446698
#define NBD_OPTS_MAGIC 0x49484156454F5054LL #define NBD_OPTS_MAGIC 0x49484156454F5054LL

View File

@ -3,6 +3,9 @@ nbd_unknown_error(int err) "Squashing unexpected error %d to EINVAL"
nbd_send_option_request(uint32_t opt, const char *name, uint32_t len) "Sending option request %" PRIu32" (%s), len %" PRIu32 nbd_send_option_request(uint32_t opt, const char *name, uint32_t len) "Sending option request %" PRIu32" (%s), len %" PRIu32
nbd_receive_option_reply(uint32_t option, const char *optname, uint32_t type, const char *typename, uint32_t length) "Received option reply %" PRIx32" (%s), type %" PRIx32" (%s), len %" PRIu32 nbd_receive_option_reply(uint32_t option, const char *optname, uint32_t type, const char *typename, uint32_t length) "Received option reply %" PRIx32" (%s), type %" PRIx32" (%s), len %" PRIu32
nbd_reply_err_unsup(uint32_t option, const char *name) "server doesn't understand request %" PRIx32 " (%s), attempting fallback" nbd_reply_err_unsup(uint32_t option, const char *name) "server doesn't understand request %" PRIx32 " (%s), attempting fallback"
nbd_opt_go_start(const char *name) "Attempting NBD_OPT_GO for export '%s'"
nbd_opt_go_success(void) "Export is good to go"
nbd_opt_go_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)"
nbd_receive_query_exports_start(const char *wantname) "Querying export list for '%s'" nbd_receive_query_exports_start(const char *wantname) "Querying export list for '%s'"
nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'" nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'"
nbd_receive_starttls_request(void) "Requesting TLS from server" nbd_receive_starttls_request(void) "Requesting TLS from server"