diff options
Diffstat (limited to 'nbd')
-rw-r--r-- | nbd/client.c | 283 | ||||
-rw-r--r-- | nbd/common.c | 92 | ||||
-rw-r--r-- | nbd/nbd-internal.h | 13 | ||||
-rw-r--r-- | nbd/server.c | 304 | ||||
-rw-r--r-- | nbd/trace-events | 25 |
5 files changed, 612 insertions, 105 deletions
diff --git a/nbd/client.c b/nbd/client.c index 9c52b9b885..c3ee9f36b1 100644 --- a/nbd/client.c +++ b/nbd/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Red Hat, Inc. + * Copyright (C) 2016-2017 Red Hat, Inc. * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * * Network Block Device Client Side @@ -104,7 +104,7 @@ static int nbd_send_option_request(QIOChannel *ioc, uint32_t opt, if (len == -1) { req.length = len = strlen(data); } - trace_nbd_send_option_request(opt, len); + trace_nbd_send_option_request(opt, nbd_opt_lookup(opt), len); stq_be_p(&req.magic, NBD_OPTS_MAGIC); stl_be_p(&req.option, opt); @@ -154,7 +154,9 @@ static int nbd_receive_option_reply(QIOChannel *ioc, uint32_t opt, be32_to_cpus(&reply->type); be32_to_cpus(&reply->length); - trace_nbd_receive_option_reply(reply->option, reply->type, reply->length); + trace_nbd_receive_option_reply(reply->option, nbd_opt_lookup(reply->option), + reply->type, nbd_rep_lookup(reply->type), + reply->length); if (reply->magic != NBD_REP_MAGIC) { error_setg(errp, "Unexpected option reply magic"); @@ -188,12 +190,16 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply, if (reply->length) { if (reply->length > NBD_MAX_BUFFER_SIZE) { - error_setg(errp, "server's error message is too long"); + error_setg(errp, "server error 0x%" PRIx32 + " (%s) message is too long", + reply->type, nbd_rep_lookup(reply->type)); goto cleanup; } msg = g_malloc(reply->length + 1); if (nbd_read(ioc, msg, reply->length, errp) < 0) { - error_prepend(errp, "failed to read option error message"); + error_prepend(errp, "failed to read option error 0x%" PRIx32 + " (%s) message", + reply->type, nbd_rep_lookup(reply->type)); goto cleanup; } msg[reply->length] = '\0'; @@ -201,38 +207,48 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply, switch (reply->type) { case NBD_REP_ERR_UNSUP: - trace_nbd_reply_err_unsup(reply->option); + trace_nbd_reply_err_unsup(reply->option, nbd_opt_lookup(reply->option)); result = 0; goto cleanup; case NBD_REP_ERR_POLICY: - error_setg(errp, "Denied by server for option %" PRIx32, - reply->option); + error_setg(errp, "Denied by server for option %" PRIx32 " (%s)", + reply->option, nbd_opt_lookup(reply->option)); break; case NBD_REP_ERR_INVALID: - error_setg(errp, "Invalid data length for option %" PRIx32, - reply->option); + error_setg(errp, "Invalid data length for option %" PRIx32 " (%s)", + reply->option, nbd_opt_lookup(reply->option)); break; case NBD_REP_ERR_PLATFORM: - error_setg(errp, "Server lacks support for option %" PRIx32, - reply->option); + error_setg(errp, "Server lacks support for option %" PRIx32 " (%s)", + reply->option, nbd_opt_lookup(reply->option)); break; case NBD_REP_ERR_TLS_REQD: - error_setg(errp, "TLS negotiation required before option %" PRIx32, - reply->option); + error_setg(errp, "TLS negotiation required before option %" PRIx32 + " (%s)", reply->option, nbd_opt_lookup(reply->option)); + break; + + case NBD_REP_ERR_UNKNOWN: + error_setg(errp, "Requested export not available for option %" PRIx32 + " (%s)", reply->option, nbd_opt_lookup(reply->option)); break; case NBD_REP_ERR_SHUTDOWN: - error_setg(errp, "Server shutting down before option %" PRIx32, - reply->option); + error_setg(errp, "Server shutting down before option %" PRIx32 " (%s)", + reply->option, nbd_opt_lookup(reply->option)); + break; + + case NBD_REP_ERR_BLOCK_SIZE_REQD: + error_setg(errp, "Server requires INFO_BLOCK_SIZE for option %" PRIx32 + " (%s)", reply->option, nbd_opt_lookup(reply->option)); break; default: - error_setg(errp, "Unknown error code when asking for option %" PRIx32, - reply->option); + error_setg(errp, "Unknown error code when asking for option %" PRIx32 + " (%s)", reply->option, nbd_opt_lookup(reply->option)); break; } @@ -334,6 +350,165 @@ 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 + 2 * info->request_sizes + 1); + stl_be_p(buf, len); + memcpy(buf + 4, wantname, len); + /* At most one request, everything else up to server */ + stw_be_p(buf + 4 + len, info->request_sizes); + if (info->request_sizes) { + stw_be_p(buf + 4 + len + 2, NBD_INFO_BLOCK_SIZE); + } + if (nbd_send_option_request(ioc, NBD_OPT_GO, + 4 + len + 2 + 2 * info->request_sizes, 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 + " (%s), expected %x", + reply.type, nbd_rep_lookup(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; + + case NBD_INFO_BLOCK_SIZE: + if (len != sizeof(info->min_block) * 3) { + error_setg(errp, "remaining export info len %" PRIu32 + " is unexpected size", len); + nbd_send_opt_abort(ioc); + return -1; + } + if (nbd_read(ioc, &info->min_block, sizeof(info->min_block), + errp) < 0) { + error_prepend(errp, "failed to read info minimum block size"); + nbd_send_opt_abort(ioc); + return -1; + } + be32_to_cpus(&info->min_block); + if (!is_power_of_2(info->min_block)) { + error_setg(errp, "server minimum block size %" PRId32 + "is not a power of two", info->min_block); + nbd_send_opt_abort(ioc); + return -1; + } + if (nbd_read(ioc, &info->opt_block, sizeof(info->opt_block), + errp) < 0) { + error_prepend(errp, "failed to read info preferred block size"); + nbd_send_opt_abort(ioc); + return -1; + } + be32_to_cpus(&info->opt_block); + if (!is_power_of_2(info->opt_block) || + info->opt_block < info->min_block) { + error_setg(errp, "server preferred block size %" PRId32 + "is not valid", info->opt_block); + nbd_send_opt_abort(ioc); + return -1; + } + if (nbd_read(ioc, &info->max_block, sizeof(info->max_block), + errp) < 0) { + error_prepend(errp, "failed to read info maximum block size"); + nbd_send_opt_abort(ioc); + return -1; + } + be32_to_cpus(&info->max_block); + trace_nbd_opt_go_info_block_size(info->min_block, info->opt_block, + info->max_block); + 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. */ static int nbd_receive_query_exports(QIOChannel *ioc, const char *wantname, @@ -425,13 +600,13 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc, } -int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags, +int nbd_receive_negotiate(QIOChannel *ioc, const char *name, QCryptoTLSCreds *tlscreds, const char *hostname, - QIOChannel **outioc, - off_t *size, Error **errp) + QIOChannel **outioc, NBDExportInfo *info, + Error **errp) { char buf[256]; - uint64_t magic, s; + uint64_t magic; int rc; bool zeroes = true; @@ -515,11 +690,25 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags, name = ""; } 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 * server export list. Since NBD_OPT_EXPORT_NAME * cannot return an error message, running this - * query gives us good error reporting if the - * server required TLS + * query gives us better error reporting if the + * export name is not available. */ if (nbd_receive_query_exports(ioc, name, errp) < 0) { goto fail; @@ -532,17 +721,17 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags, } /* Read the response */ - if (nbd_read(ioc, &s, sizeof(s), errp) < 0) { + if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) { error_prepend(errp, "Failed to read export length"); goto fail; } - *size = be64_to_cpu(s); + be64_to_cpus(&info->size); - if (nbd_read(ioc, flags, sizeof(*flags), errp) < 0) { + if (nbd_read(ioc, &info->flags, sizeof(info->flags), errp) < 0) { error_prepend(errp, "Failed to read export flags"); goto fail; } - be16_to_cpus(flags); + be16_to_cpus(&info->flags); } else if (magic == NBD_CLIENT_MAGIC) { uint32_t oldflags; @@ -555,11 +744,11 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags, goto fail; } - if (nbd_read(ioc, &s, sizeof(s), errp) < 0) { + if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) { error_prepend(errp, "Failed to read export length"); goto fail; } - *size = be64_to_cpu(s); + be64_to_cpus(&info->size); if (nbd_read(ioc, &oldflags, sizeof(oldflags), errp) < 0) { error_prepend(errp, "Failed to read export flags"); @@ -570,13 +759,13 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags, error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags); goto fail; } - *flags = oldflags; + info->flags = oldflags; } else { error_setg(errp, "Bad magic received"); goto fail; } - trace_nbd_receive_negotiate_size_flags(*size, *flags); + trace_nbd_receive_negotiate_size_flags(info->size, info->flags); if (zeroes && nbd_drop(ioc, 124, errp) < 0) { error_prepend(errp, "Failed to read reserved block"); goto fail; @@ -588,13 +777,19 @@ fail: } #ifdef __linux__ -int nbd_init(int fd, QIOChannelSocket *sioc, uint16_t flags, off_t size, +int nbd_init(int fd, QIOChannelSocket *sioc, NBDExportInfo *info, Error **errp) { - unsigned long sectors = size / BDRV_SECTOR_SIZE; - if (size / BDRV_SECTOR_SIZE != sectors) { - error_setg(errp, "Export size %lld too large for 32-bit kernel", - (long long) size); + unsigned long sector_size = MAX(BDRV_SECTOR_SIZE, info->min_block); + unsigned long sectors = info->size / sector_size; + + /* FIXME: Once the kernel module is patched to honor block sizes, + * and to advertise that fact to user space, we should update the + * hand-off to the kernel to use any block sizes we learned. */ + assert(!info->request_sizes); + if (info->size / sector_size != sectors) { + error_setg(errp, "Export size %" PRIu64 " too large for 32-bit kernel", + info->size); return -E2BIG; } @@ -606,17 +801,17 @@ int nbd_init(int fd, QIOChannelSocket *sioc, uint16_t flags, off_t size, return -serrno; } - trace_nbd_init_set_block_size(BDRV_SECTOR_SIZE); + trace_nbd_init_set_block_size(sector_size); - if (ioctl(fd, NBD_SET_BLKSIZE, (unsigned long)BDRV_SECTOR_SIZE) < 0) { + if (ioctl(fd, NBD_SET_BLKSIZE, sector_size) < 0) { int serrno = errno; error_setg(errp, "Failed setting NBD block size"); return -serrno; } trace_nbd_init_set_size(sectors); - if (size % BDRV_SECTOR_SIZE) { - trace_nbd_init_trailing_bytes(size % BDRV_SECTOR_SIZE); + if (info->size % sector_size) { + trace_nbd_init_trailing_bytes(info->size % sector_size); } if (ioctl(fd, NBD_SET_SIZE_BLOCKS, sectors) < 0) { @@ -625,9 +820,9 @@ int nbd_init(int fd, QIOChannelSocket *sioc, uint16_t flags, off_t size, return -serrno; } - if (ioctl(fd, NBD_SET_FLAGS, (unsigned long) flags) < 0) { + if (ioctl(fd, NBD_SET_FLAGS, (unsigned long) info->flags) < 0) { if (errno == ENOTTY) { - int read_only = (flags & NBD_FLAG_READ_ONLY) != 0; + int read_only = (info->flags & NBD_FLAG_READ_ONLY) != 0; trace_nbd_init_set_readonly(); if (ioctl(fd, BLKROSET, (unsigned long) &read_only) < 0) { @@ -685,7 +880,7 @@ int nbd_disconnect(int fd) } #else -int nbd_init(int fd, QIOChannelSocket *ioc, uint16_t flags, off_t size, +int nbd_init(int fd, QIOChannelSocket *ioc, NBDExportInfo *info, Error **errp) { error_setg(errp, "nbd_init is only supported on Linux"); diff --git a/nbd/common.c b/nbd/common.c index 4dab41e2c0..a2f28f2eec 100644 --- a/nbd/common.c +++ b/nbd/common.c @@ -101,3 +101,95 @@ void nbd_tls_handshake(QIOTask *task, data->complete = true; g_main_loop_quit(data->loop); } + + +const char *nbd_opt_lookup(uint32_t opt) +{ + switch (opt) { + case NBD_OPT_EXPORT_NAME: + return "export name"; + case NBD_OPT_ABORT: + return "abort"; + case NBD_OPT_LIST: + return "list"; + case NBD_OPT_STARTTLS: + return "starttls"; + case NBD_OPT_INFO: + return "info"; + case NBD_OPT_GO: + return "go"; + case NBD_OPT_STRUCTURED_REPLY: + return "structured reply"; + default: + return "<unknown>"; + } +} + + +const char *nbd_rep_lookup(uint32_t rep) +{ + switch (rep) { + case NBD_REP_ACK: + return "ack"; + case NBD_REP_SERVER: + return "server"; + case NBD_REP_INFO: + return "info"; + case NBD_REP_ERR_UNSUP: + return "unsupported"; + case NBD_REP_ERR_POLICY: + return "denied by policy"; + case NBD_REP_ERR_INVALID: + return "invalid"; + case NBD_REP_ERR_PLATFORM: + return "platform lacks support"; + case NBD_REP_ERR_TLS_REQD: + return "TLS required"; + case NBD_REP_ERR_UNKNOWN: + return "export unknown"; + case NBD_REP_ERR_SHUTDOWN: + return "server shutting down"; + case NBD_REP_ERR_BLOCK_SIZE_REQD: + return "block size required"; + default: + return "<unknown>"; + } +} + + +const char *nbd_info_lookup(uint16_t info) +{ + switch (info) { + case NBD_INFO_EXPORT: + return "export"; + case NBD_INFO_NAME: + return "name"; + case NBD_INFO_DESCRIPTION: + return "description"; + case NBD_INFO_BLOCK_SIZE: + return "block size"; + default: + return "<unknown>"; + } +} + + +const char *nbd_cmd_lookup(uint16_t cmd) +{ + switch (cmd) { + case NBD_CMD_READ: + return "read"; + case NBD_CMD_WRITE: + return "write"; + case NBD_CMD_DISC: + return "discard"; + case NBD_CMD_FLUSH: + return "flush"; + case NBD_CMD_TRIM: + return "trim"; + case NBD_CMD_WRITE_ZEROES: + return "write zeroes"; + default: + return "<unknown>"; + } +} diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h index cf6ecbf358..4065bc68ac 100644 --- a/nbd/nbd-internal.h +++ b/nbd/nbd-internal.h @@ -37,8 +37,11 @@ * 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) +/* Size of all NBD_REP_* sent in answer to most NBD_OPT_*, without payload */ #define NBD_REPLY_SIZE (4 + 4 + 8) + #define NBD_REQUEST_MAGIC 0x25609513 #define NBD_REPLY_MAGIC 0x67446698 #define NBD_OPTS_MAGIC 0x49484156454F5054LL @@ -57,12 +60,6 @@ #define NBD_SET_TIMEOUT _IO(0xab, 9) #define NBD_SET_FLAGS _IO(0xab, 10) -#define NBD_OPT_EXPORT_NAME (1) -#define NBD_OPT_ABORT (2) -#define NBD_OPT_LIST (3) -#define NBD_OPT_PEEK_EXPORT (4) -#define NBD_OPT_STARTTLS (5) - /* NBD errors are based on errno numbers, so there is a 1:1 mapping, * but only a limited set of errno values is specified in the protocol. * Everything else is squashed to EINVAL. @@ -133,6 +130,10 @@ struct NBDTLSHandshakeData { void nbd_tls_handshake(QIOTask *task, void *opaque); +const char *nbd_opt_lookup(uint32_t opt); +const char *nbd_rep_lookup(uint32_t rep); +const char *nbd_info_lookup(uint16_t info); +const char *nbd_cmd_lookup(uint16_t info); int nbd_drop(QIOChannel *ioc, size_t size, Error **errp); diff --git a/nbd/server.c b/nbd/server.c index 9b0c588146..49ed57455c 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Red Hat, Inc. + * Copyright (C) 2016-2017 Red Hat, Inc. * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * * Network Block Device Server Side @@ -84,7 +84,6 @@ struct NBDClient { int refcount; void (*close_fn)(NBDClient *client, bool negotiated); - bool no_zeroes; NBDExport *exp; QCryptoTLSCreds *tlscreds; char *tlsaclname; @@ -139,8 +138,10 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type, { uint64_t magic; - trace_nbd_negotiate_send_rep_len(opt, type, len); + trace_nbd_negotiate_send_rep_len(opt, nbd_opt_lookup(opt), + type, nbd_rep_lookup(type), len); + assert(len < NBD_MAX_BUFFER_SIZE); magic = cpu_to_be64(NBD_REP_MAGIC); if (nbd_write(ioc, &magic, sizeof(magic), errp) < 0) { error_prepend(errp, "write failed (rep magic): "); @@ -275,10 +276,16 @@ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length, return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST, errp); } +/* Send a reply to NBD_OPT_EXPORT_NAME. + * Return -errno on error, 0 on success. */ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, + uint16_t myflags, bool no_zeroes, Error **errp) { char name[NBD_MAX_NAME_SIZE + 1]; + char buf[8 + 4 + 124] = ""; + size_t len; + int ret; /* Client sends: [20 .. xx] export name (length bytes) @@ -302,12 +309,219 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, return -EINVAL; } + trace_nbd_negotiate_new_style_size_flags(client->exp->size, + client->exp->nbdflags | myflags); + stq_be_p(buf, client->exp->size); + stw_be_p(buf + 8, client->exp->nbdflags | myflags); + len = no_zeroes ? 10 : sizeof(buf); + ret = nbd_write(client->ioc, buf, len, errp); + if (ret < 0) { + error_prepend(errp, "write failed: "); + return ret; + } + QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); nbd_export_get(client->exp); return 0; } +/* Send a single NBD_REP_INFO, with a buffer @buf of @length bytes. + * The buffer does NOT include the info type prefix. + * Return -errno on error, 0 if ready to send more. */ +static int nbd_negotiate_send_info(NBDClient *client, uint32_t opt, + uint16_t info, uint32_t length, void *buf, + Error **errp) +{ + int rc; + + trace_nbd_negotiate_send_info(info, nbd_info_lookup(info), length); + rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt, + sizeof(info) + length, errp); + if (rc < 0) { + return rc; + } + cpu_to_be16s(&info); + if (nbd_write(client->ioc, &info, sizeof(info), errp) < 0) { + return -EIO; + } + if (nbd_write(client->ioc, buf, length, errp) < 0) { + return -EIO; + } + return 0; +} + +/* Handle NBD_OPT_INFO and NBD_OPT_GO. + * Return -errno on error, 0 if ready for next option, and 1 to move + * into transmission phase. */ +static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, + uint32_t opt, uint16_t myflags, + Error **errp) +{ + int rc; + char name[NBD_MAX_NAME_SIZE + 1]; + NBDExport *exp; + uint16_t requests; + uint16_t request; + uint32_t namelen; + bool sendname = false; + bool blocksize = false; + uint32_t sizes[3]; + char buf[sizeof(uint64_t) + sizeof(uint16_t)]; + const char *msg; + + /* Client sends: + 4 bytes: L, name length (can be 0) + L bytes: export name + 2 bytes: N, number of requests (can be 0) + N * 2 bytes: N requests + */ + if (length < sizeof(namelen) + sizeof(requests)) { + msg = "overall request too short"; + goto invalid; + } + if (nbd_read(client->ioc, &namelen, sizeof(namelen), errp) < 0) { + return -EIO; + } + be32_to_cpus(&namelen); + length -= sizeof(namelen); + if (namelen > length - sizeof(requests) || (length - namelen) % 2) { + msg = "name length is incorrect"; + goto invalid; + } + if (nbd_read(client->ioc, name, namelen, errp) < 0) { + return -EIO; + } + name[namelen] = '\0'; + length -= namelen; + trace_nbd_negotiate_handle_export_name_request(name); + + if (nbd_read(client->ioc, &requests, sizeof(requests), errp) < 0) { + return -EIO; + } + be16_to_cpus(&requests); + length -= sizeof(requests); + trace_nbd_negotiate_handle_info_requests(requests); + if (requests != length / sizeof(request)) { + msg = "incorrect number of requests for overall length"; + goto invalid; + } + while (requests--) { + if (nbd_read(client->ioc, &request, sizeof(request), errp) < 0) { + return -EIO; + } + be16_to_cpus(&request); + length -= sizeof(request); + trace_nbd_negotiate_handle_info_request(request, + nbd_info_lookup(request)); + /* We care about NBD_INFO_NAME and NBD_INFO_BLOCK_SIZE; + * everything else is either a request we don't know or + * something we send regardless of request */ + switch (request) { + case NBD_INFO_NAME: + sendname = true; + break; + case NBD_INFO_BLOCK_SIZE: + blocksize = true; + break; + } + } + + exp = nbd_export_find(name); + if (!exp) { + return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN, + opt, errp, "export '%s' not present", + name); + } + + /* Don't bother sending NBD_INFO_NAME unless client requested it */ + if (sendname) { + rc = nbd_negotiate_send_info(client, opt, NBD_INFO_NAME, length, name, + errp); + if (rc < 0) { + return rc; + } + } + + /* Send NBD_INFO_DESCRIPTION only if available, regardless of + * client request */ + if (exp->description) { + size_t len = strlen(exp->description); + + rc = nbd_negotiate_send_info(client, opt, NBD_INFO_DESCRIPTION, + len, exp->description, errp); + if (rc < 0) { + return rc; + } + } + + /* Send NBD_INFO_BLOCK_SIZE always, but tweak the minimum size + * according to whether the client requested it, and according to + * whether this is OPT_INFO or OPT_GO. */ + /* minimum - 1 for back-compat, or 512 if client is new enough. + * TODO: consult blk_bs(blk)->bl.request_alignment? */ + sizes[0] = (opt == NBD_OPT_INFO || blocksize) ? BDRV_SECTOR_SIZE : 1; + /* preferred - Hard-code to 4096 for now. + * TODO: is blk_bs(blk)->bl.opt_transfer appropriate? */ + sizes[1] = 4096; + /* maximum - At most 32M, but smaller as appropriate. */ + sizes[2] = MIN(blk_get_max_transfer(exp->blk), NBD_MAX_BUFFER_SIZE); + trace_nbd_negotiate_handle_info_block_size(sizes[0], sizes[1], sizes[2]); + cpu_to_be32s(&sizes[0]); + cpu_to_be32s(&sizes[1]); + cpu_to_be32s(&sizes[2]); + rc = nbd_negotiate_send_info(client, opt, NBD_INFO_BLOCK_SIZE, + sizeof(sizes), sizes, errp); + if (rc < 0) { + return rc; + } + + /* Send NBD_INFO_EXPORT always */ + trace_nbd_negotiate_new_style_size_flags(exp->size, + exp->nbdflags | myflags); + stq_be_p(buf, exp->size); + stw_be_p(buf + 8, exp->nbdflags | myflags); + rc = nbd_negotiate_send_info(client, opt, NBD_INFO_EXPORT, + sizeof(buf), buf, errp); + if (rc < 0) { + return rc; + } + + /* If the client is just asking for NBD_OPT_INFO, but forgot to + * request block sizes, return an error. + * TODO: consult blk_bs(blk)->request_align, and only error if it + * is not 1? */ + if (opt == NBD_OPT_INFO && !blocksize) { + return nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_BLOCK_SIZE_REQD, opt, + errp, + "request NBD_INFO_BLOCK_SIZE to " + "use this export"); + } + + /* Final reply */ + rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt, errp); + if (rc < 0) { + return rc; + } + + if (opt == NBD_OPT_GO) { + client->exp = exp; + QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); + nbd_export_get(client->exp); + rc = 1; + } + return rc; + + invalid: + if (nbd_drop(client->ioc, length, errp) < 0) { + return -EIO; + } + return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, opt, + errp, "%s", msg); +} + + /* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the * new channel for all further (now-encrypted) communication. */ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, @@ -365,22 +579,25 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, } /* nbd_negotiate_options - * Process all NBD_OPT_* client option commands. + * Process all NBD_OPT_* client option commands, during fixed newstyle + * negotiation. * Return: * -errno on error, errp is set * 0 on successful negotiation, errp is not set * 1 if client sent NBD_OPT_ABORT, i.e. on valid disconnect, * errp is not set */ -static int nbd_negotiate_options(NBDClient *client, Error **errp) +static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, + Error **errp) { uint32_t flags; bool fixedNewstyle = false; - Error *local_err = NULL; + bool no_zeroes = false; /* Client sends: [ 0 .. 3] client flags + Then we loop until NBD_OPT_EXPORT_NAME or NBD_OPT_GO: [ 0 .. 7] NBD_OPTS_MAGIC [ 8 .. 11] NBD option [12 .. 15] Data length @@ -396,21 +613,19 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) error_prepend(errp, "read failed: "); return -EIO; } - trace_nbd_negotiate_options_flags(); be32_to_cpus(&flags); + trace_nbd_negotiate_options_flags(flags); if (flags & NBD_FLAG_C_FIXED_NEWSTYLE) { - trace_nbd_negotiate_options_newstyle(); fixedNewstyle = true; flags &= ~NBD_FLAG_C_FIXED_NEWSTYLE; } if (flags & NBD_FLAG_C_NO_ZEROES) { - trace_nbd_negotiate_options_no_zeroes(); - client->no_zeroes = true; + no_zeroes = true; flags &= ~NBD_FLAG_C_NO_ZEROES; } if (flags != 0) { error_setg(errp, "Unknown client flags 0x%" PRIx32 " received", flags); - return -EIO; + return -EINVAL; } while (1) { @@ -442,7 +657,8 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) } length = be32_to_cpu(length); - trace_nbd_negotiate_options_check_option(option); + trace_nbd_negotiate_options_check_option(option, + nbd_opt_lookup(option)); if (client->tlscreds && client->ioc == (QIOChannel *)client->sioc) { QIOChannel *tioc; @@ -479,7 +695,9 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) if (ret < 0) { return ret; } - /* Let the client keep trying, unless they asked to quit */ + /* Let the client keep trying, unless they asked to + * quit. In this mode, we've already sent an error, so + * we can't ack the abort. */ if (option == NBD_OPT_ABORT) { return 1; } @@ -498,19 +716,26 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) /* NBD spec says we must try to reply before * disconnecting, but that we must also tolerate * guests that don't wait for our reply. */ - nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, option, - &local_err); - - if (local_err != NULL) { - const char *error = error_get_pretty(local_err); - trace_nbd_opt_abort_reply_failed(error); - error_free(local_err); - } - + nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, option, NULL); return 1; case NBD_OPT_EXPORT_NAME: - return nbd_negotiate_handle_export_name(client, length, errp); + return nbd_negotiate_handle_export_name(client, length, + myflags, no_zeroes, + errp); + + case NBD_OPT_INFO: + case NBD_OPT_GO: + ret = nbd_negotiate_handle_info(client, length, option, + myflags, errp); + if (ret == 1) { + assert(option == NBD_OPT_GO); + return 0; + } + if (ret) { + return ret; + } + break; case NBD_OPT_STARTTLS: if (nbd_drop(client->ioc, length, errp) < 0) { @@ -539,8 +764,8 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) NBD_REP_ERR_UNSUP, option, errp, "Unsupported option 0x%" - PRIx32, - option); + PRIx32 " (%s)", option, + nbd_opt_lookup(option)); if (ret < 0) { return ret; } @@ -553,10 +778,13 @@ static int nbd_negotiate_options(NBDClient *client, Error **errp) */ switch (option) { case NBD_OPT_EXPORT_NAME: - return nbd_negotiate_handle_export_name(client, length, errp); + return nbd_negotiate_handle_export_name(client, length, + myflags, no_zeroes, + errp); default: - error_setg(errp, "Unsupported option 0x%" PRIx32, option); + error_setg(errp, "Unsupported option 0x%" PRIx32 " (%s)", + option, nbd_opt_lookup(option)); return -EINVAL; } } @@ -578,7 +806,6 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_WRITE_ZEROES); bool oldStyle; - size_t len; /* Old style negotiation header without options [ 0 .. 7] passwd ("NBDMAGIC") @@ -592,10 +819,7 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) [ 0 .. 7] passwd ("NBDMAGIC") [ 8 .. 15] magic (NBD_OPTS_MAGIC) [16 .. 17] server flags (0) - ....options sent.... - [18 .. 25] size - [26 .. 27] export flags - [28 .. 151] reserved (0, omit if no_zeroes) + ....options sent, ending in NBD_OPT_EXPORT_NAME or NBD_OPT_GO.... */ qio_channel_set_blocking(client->ioc, false, NULL); @@ -624,24 +848,13 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) error_prepend(errp, "write failed: "); return -EINVAL; } - ret = nbd_negotiate_options(client, errp); + ret = nbd_negotiate_options(client, myflags, errp); if (ret != 0) { if (ret < 0) { error_prepend(errp, "option negotiation failed: "); } return ret; } - - trace_nbd_negotiate_new_style_size_flags( - client->exp->size, client->exp->nbdflags | myflags); - stq_be_p(buf + 18, client->exp->size); - stw_be_p(buf + 26, client->exp->nbdflags | myflags); - len = client->no_zeroes ? 10 : sizeof(buf) - 18; - ret = nbd_write(client->ioc, buf + 18, len, errp); - if (ret < 0) { - error_prepend(errp, "write failed: "); - return ret; - } } trace_nbd_negotiate_success(); @@ -1040,7 +1253,8 @@ static int nbd_co_receive_request(NBDRequestData *req, NBDRequest *request, return -EIO; } - trace_nbd_co_receive_request_decode_type(request->handle, request->type); + trace_nbd_co_receive_request_decode_type(request->handle, request->type, + nbd_cmd_lookup(request->type)); if (request->type != NBD_CMD_WRITE) { /* No payload, we are ready to read the next request. */ diff --git a/nbd/trace-events b/nbd/trace-events index 4b233b8510..f5024d85a1 100644 --- a/nbd/trace-events +++ b/nbd/trace-events @@ -1,8 +1,12 @@ # nbd/client.c nbd_unknown_error(int err) "Squashing unexpected error %d to EINVAL" -nbd_send_option_request(uint32_t opt, uint32_t len) "Sending option request %" PRIu32", len %" PRIu32 -nbd_receive_option_reply(uint32_t option, uint32_t type, uint32_t length) "Received option reply %" PRIx32", type %" PRIx32", len %" PRIu32 -nbd_reply_err_unsup(uint32_t option) "server doesn't understand request %" PRIx32 ", attempting fallback" +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_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_opt_go_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "Block sizes are 0x%" PRIx32 ", 0x%" PRIx32 ", 0x%" PRIx32 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_starttls_request(void) "Requesting TLS from server" @@ -28,19 +32,20 @@ nbd_send_request(uint64_t from, uint32_t len, uint64_t handle, uint16_t flags, u nbd_receive_reply(uint32_t magic, int32_t error, uint64_t handle) "Got reply: { magic = 0x%" PRIx32 ", .error = % " PRId32 ", handle = %" PRIu64" }" # nbd/server.c -nbd_negotiate_send_rep_len(uint32_t opt, uint32_t type, uint32_t len) "Reply opt=%" PRIx32 " type=%" PRIx32 " len=%" PRIu32 +nbd_negotiate_send_rep_len(uint32_t opt, const char *optname, uint32_t type, const char *typename, uint32_t len) "Reply opt=%" PRIx32 " (%s), type=%" PRIx32 " (%s), len=%" PRIu32 nbd_negotiate_send_rep_err(const char *msg) "sending error message \"%s\"" nbd_negotiate_send_rep_list(const char *name, const char *desc) "Advertising export name '%s' description '%s'" nbd_negotiate_handle_export_name(void) "Checking length" nbd_negotiate_handle_export_name_request(const char *name) "Client requested export '%s'" +nbd_negotiate_send_info(int info, const char *name, uint32_t length) "Sending NBD_REP_INFO type %d (%s) with remaining length %" PRIu32 +nbd_negotiate_handle_info_requests(int requests) "Client requested %d items of info" +nbd_negotiate_handle_info_request(int request, const char *name) "Client requested info %d (%s)" +nbd_negotiate_handle_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "advertising minimum 0x%" PRIx32 ", preferred 0x%" PRIx32 ", maximum 0x%" PRIx32 nbd_negotiate_handle_starttls(void) "Setting up TLS" nbd_negotiate_handle_starttls_handshake(void) "Starting TLS handshake" -nbd_negotiate_options_flags(void) "Checking client flags" -nbd_negotiate_options_newstyle(void) "Client supports fixed newstyle handshake" -nbd_negotiate_options_no_zeroes(void) "Client supports no zeroes at handshake end" +nbd_negotiate_options_flags(uint32_t flags) "Received client flags 0x%" PRIx32 nbd_negotiate_options_check_magic(uint64_t magic) "Checking opts magic 0x%" PRIx64 -nbd_negotiate_options_check_option(uint32_t option) "Checking option 0x%" PRIx32 -nbd_opt_abort_reply_failed(const char *error) "Reply to NBD_OPT_ABORT request failed: %s" +nbd_negotiate_options_check_option(uint32_t option, const char *name) "Checking option 0x%" PRIx32 " (%s)" nbd_negotiate_begin(void) "Beginning negotiation" nbd_negotiate_old_style(uint64_t size, unsigned flags) "advertising size %" PRIu64 " and flags %x" nbd_negotiate_new_style_size_flags(uint64_t size, unsigned flags) "advertising size %" PRIu64 " and flags %x" @@ -50,7 +55,7 @@ nbd_send_reply(int32_t error, uint64_t handle) "Sending response to client: { .e nbd_blk_aio_attached(const char *name, void *ctx) "Export %s: Attaching clients to AIO context %p\n" nbd_blk_aio_detach(const char *name, void *ctx) "Export %s: Detaching clients from AIO context %p\n" nbd_co_send_reply(uint64_t handle, uint32_t error, int len) "Send reply: handle = %" PRIu64 ", error = %" PRIu32 ", len = %d" -nbd_co_receive_request_decode_type(uint64_t handle, uint16_t type) "Decoding type: handle = %" PRIu64 ", type = %" PRIu16 +nbd_co_receive_request_decode_type(uint64_t handle, uint16_t type, const char *name) "Decoding type: handle = %" PRIu64 ", type = %" PRIu16 " (%s)" nbd_co_receive_request_payload_received(uint64_t handle, uint32_t len) "Payload received: handle = %" PRIu64 ", len = %" PRIu32 nbd_co_receive_request_cmd_write(uint32_t len) "Reading %" PRIu32 " byte(s)" nbd_trip(void) "Reading request" |