diff options
-rw-r--r-- | nbd/server.c | 342 |
1 files changed, 171 insertions, 171 deletions
diff --git a/nbd/server.c b/nbd/server.c index 6cf2eeb2c1..6caa8d17be 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2017 Red Hat, Inc. + * Copyright (C) 2016-2018 Red Hat, Inc. * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * * Network Block Device Server Side @@ -102,9 +102,11 @@ struct NBDClient { bool closing; bool structured_reply; -}; -/* That's all folks */ + uint32_t opt; /* Current option being negotiated */ + uint32_t optlen; /* remaining length of data in ioc for the option being + negotiated now */ +}; static void nbd_client_receive_next_request(NBDClient *client); @@ -135,73 +137,58 @@ static void nbd_client_receive_next_request(NBDClient *client); */ +static inline void set_be_option_rep(NBDOptionReply *rep, uint32_t option, + uint32_t type, uint32_t length) +{ + stq_be_p(&rep->magic, NBD_REP_MAGIC); + stl_be_p(&rep->option, option); + stl_be_p(&rep->type, type); + stl_be_p(&rep->length, length); +} + /* Send a reply header, including length, but no payload. * Return -errno on error, 0 on success. */ -static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type, - uint32_t opt, uint32_t len, Error **errp) +static int nbd_negotiate_send_rep_len(NBDClient *client, uint32_t type, + uint32_t len, Error **errp) { - uint64_t magic; + NBDOptionReply rep; - trace_nbd_negotiate_send_rep_len(opt, nbd_opt_lookup(opt), + trace_nbd_negotiate_send_rep_len(client->opt, nbd_opt_lookup(client->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): "); - return -EINVAL; - } - - opt = cpu_to_be32(opt); - if (nbd_write(ioc, &opt, sizeof(opt), errp) < 0) { - error_prepend(errp, "write failed (rep opt): "); - return -EINVAL; - } - - type = cpu_to_be32(type); - if (nbd_write(ioc, &type, sizeof(type), errp) < 0) { - error_prepend(errp, "write failed (rep type): "); - return -EINVAL; - } - len = cpu_to_be32(len); - if (nbd_write(ioc, &len, sizeof(len), errp) < 0) { - error_prepend(errp, "write failed (rep data length): "); - return -EINVAL; - } - return 0; + set_be_option_rep(&rep, client->opt, type, len); + return nbd_write(client->ioc, &rep, sizeof(rep), errp); } /* Send a reply header with default 0 length. * Return -errno on error, 0 on success. */ -static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt, +static int nbd_negotiate_send_rep(NBDClient *client, uint32_t type, Error **errp) { - return nbd_negotiate_send_rep_len(ioc, type, opt, 0, errp); + return nbd_negotiate_send_rep_len(client, type, 0, errp); } /* Send an error reply. * Return -errno on error, 0 on success. */ -static int GCC_FMT_ATTR(5, 6) -nbd_negotiate_send_rep_err(QIOChannel *ioc, uint32_t type, - uint32_t opt, Error **errp, const char *fmt, ...) +static int GCC_FMT_ATTR(4, 0) +nbd_negotiate_send_rep_verr(NBDClient *client, uint32_t type, + Error **errp, const char *fmt, va_list va) { - va_list va; char *msg; int ret; size_t len; - va_start(va, fmt); msg = g_strdup_vprintf(fmt, va); - va_end(va); len = strlen(msg); assert(len < 4096); trace_nbd_negotiate_send_rep_err(msg); - ret = nbd_negotiate_send_rep_len(ioc, type, opt, len, errp); + ret = nbd_negotiate_send_rep_len(client, type, len, errp); if (ret < 0) { goto out; } - if (nbd_write(ioc, msg, len, errp) < 0) { + if (nbd_write(client->ioc, msg, len, errp) < 0) { error_prepend(errp, "write failed (error message): "); ret = -EIO; } else { @@ -213,23 +200,72 @@ out: return ret; } +/* Send an error reply. + * Return -errno on error, 0 on success. */ +static int GCC_FMT_ATTR(4, 5) +nbd_negotiate_send_rep_err(NBDClient *client, uint32_t type, + Error **errp, const char *fmt, ...) +{ + va_list va; + int ret; + + va_start(va, fmt); + ret = nbd_negotiate_send_rep_verr(client, type, errp, fmt, va); + va_end(va); + return ret; +} + +/* Drop remainder of the current option, and send a reply with the + * given error type and message. Return -errno on read or write + * failure; or 0 if connection is still live. */ +static int GCC_FMT_ATTR(4, 5) +nbd_opt_drop(NBDClient *client, uint32_t type, Error **errp, + const char *fmt, ...) +{ + int ret = nbd_drop(client->ioc, client->optlen, errp); + va_list va; + + client->optlen = 0; + if (!ret) { + va_start(va, fmt); + ret = nbd_negotiate_send_rep_verr(client, type, errp, fmt, va); + va_end(va); + } + return ret; +} + +/* Read size bytes from the unparsed payload of the current option. + * Return -errno on I/O error, 0 if option was completely handled by + * sending a reply about inconsistent lengths, or 1 on success. */ +static int nbd_opt_read(NBDClient *client, void *buffer, size_t size, + Error **errp) +{ + if (size > client->optlen) { + return nbd_opt_drop(client, NBD_REP_ERR_INVALID, errp, + "Inconsistent lengths in option %s", + nbd_opt_lookup(client->opt)); + } + client->optlen -= size; + return qio_channel_read_all(client->ioc, buffer, size, errp) < 0 ? -EIO : 1; +} + /* Send a single NBD_REP_SERVER reply to NBD_OPT_LIST, including payload. * Return -errno on error, 0 on success. */ -static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp, +static int nbd_negotiate_send_rep_list(NBDClient *client, NBDExport *exp, Error **errp) { size_t name_len, desc_len; uint32_t len; const char *name = exp->name ? exp->name : ""; const char *desc = exp->description ? exp->description : ""; + QIOChannel *ioc = client->ioc; int ret; trace_nbd_negotiate_send_rep_list(name, desc); name_len = strlen(name); desc_len = strlen(desc); len = name_len + desc_len + sizeof(len); - ret = nbd_negotiate_send_rep_len(ioc, NBD_REP_SERVER, NBD_OPT_LIST, len, - errp); + ret = nbd_negotiate_send_rep_len(client, NBD_REP_SERVER, len, errp); if (ret < 0) { return ret; } @@ -258,20 +294,21 @@ static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp, static int nbd_negotiate_handle_list(NBDClient *client, Error **errp) { NBDExport *exp; + assert(client->opt == NBD_OPT_LIST); /* For each export, send a NBD_REP_SERVER reply. */ QTAILQ_FOREACH(exp, &exports, next) { - if (nbd_negotiate_send_rep_list(client->ioc, exp, errp)) { + if (nbd_negotiate_send_rep_list(client, exp, errp)) { return -EINVAL; } } /* Finish with a NBD_REP_ACK. */ - return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST, errp); + return nbd_negotiate_send_rep(client, NBD_REP_ACK, 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, +static int nbd_negotiate_handle_export_name(NBDClient *client, uint16_t myflags, bool no_zeroes, Error **errp) { @@ -288,15 +325,16 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, [10 .. 133] reserved (0) [unless no_zeroes] */ trace_nbd_negotiate_handle_export_name(); - if (length >= sizeof(name)) { + if (client->optlen >= sizeof(name)) { error_setg(errp, "Bad length received"); return -EINVAL; } - if (nbd_read(client->ioc, name, length, errp) < 0) { + if (nbd_read(client->ioc, name, client->optlen, errp) < 0) { error_prepend(errp, "read failed: "); - return -EINVAL; + return -EIO; } - name[length] = '\0'; + name[client->optlen] = '\0'; + client->optlen = 0; trace_nbd_negotiate_handle_export_name_request(name); @@ -326,14 +364,14 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, /* 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, +static int nbd_negotiate_send_info(NBDClient *client, 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, + rc = nbd_negotiate_send_rep_len(client, NBD_REP_INFO, sizeof(info) + length, errp); if (rc < 0) { return rc; @@ -348,11 +386,33 @@ static int nbd_negotiate_send_info(NBDClient *client, uint32_t opt, return 0; } +/* nbd_reject_length: Handle any unexpected payload. + * @fatal requests that we quit talking to the client, even if we are able + * to successfully send an error reply. + * Return: + * -errno transmission error occurred or @fatal was requested, errp is set + * 0 error message successfully sent to client, errp is not set + */ +static int nbd_reject_length(NBDClient *client, bool fatal, Error **errp) +{ + int ret; + + assert(client->optlen); + ret = nbd_opt_drop(client, NBD_REP_ERR_INVALID, errp, + "option '%s' has unexpected length", + nbd_opt_lookup(client->opt)); + if (fatal && !ret) { + error_setg(errp, "option '%s' has unexpected length", + nbd_opt_lookup(client->opt)); + return -EINVAL; + } + return ret; +} + /* 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, +static int nbd_negotiate_handle_info(NBDClient *client, uint16_t myflags, Error **errp) { int rc; @@ -365,7 +425,6 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, 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) @@ -373,46 +432,34 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, 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; + rc = nbd_opt_read(client, &namelen, sizeof(namelen), errp); + if (rc <= 0) { + return rc; } be32_to_cpus(&namelen); - length -= sizeof(namelen); - if (namelen > length - sizeof(requests) || (length - namelen) % 2) { - msg = "name length is incorrect"; - goto invalid; - } if (namelen >= sizeof(name)) { - msg = "name too long for qemu"; - goto invalid; + return nbd_opt_drop(client, NBD_REP_ERR_INVALID, errp, + "name too long for qemu"); } - if (nbd_read(client->ioc, name, namelen, errp) < 0) { - return -EIO; + rc = nbd_opt_read(client, name, namelen, errp); + if (rc <= 0) { + return rc; } 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; + rc = nbd_opt_read(client, &requests, sizeof(requests), errp); + if (rc <= 0) { + return rc; } 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; + rc = nbd_opt_read(client, &request, sizeof(request), errp); + if (rc <= 0) { + return rc; } 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; @@ -427,18 +474,20 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, break; } } - assert(length == 0); + if (client->optlen) { + return nbd_reject_length(client, false, errp); + } 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", + return nbd_negotiate_send_rep_err(client, NBD_REP_ERR_UNKNOWN, + 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, namelen, name, + rc = nbd_negotiate_send_info(client, NBD_INFO_NAME, namelen, name, errp); if (rc < 0) { return rc; @@ -450,7 +499,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, if (exp->description) { size_t len = strlen(exp->description); - rc = nbd_negotiate_send_info(client, opt, NBD_INFO_DESCRIPTION, + rc = nbd_negotiate_send_info(client, NBD_INFO_DESCRIPTION, len, exp->description, errp); if (rc < 0) { return rc; @@ -462,7 +511,8 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, * 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; + sizes[0] = + (client->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; @@ -472,7 +522,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, 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, + rc = nbd_negotiate_send_info(client, NBD_INFO_BLOCK_SIZE, sizeof(sizes), sizes, errp); if (rc < 0) { return rc; @@ -483,7 +533,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, 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, + rc = nbd_negotiate_send_info(client, NBD_INFO_EXPORT, sizeof(buf), buf, errp); if (rc < 0) { return rc; @@ -493,34 +543,27 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, * 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, + if (client->opt == NBD_OPT_INFO && !blocksize) { + return nbd_negotiate_send_rep_err(client, + NBD_REP_ERR_BLOCK_SIZE_REQD, errp, "request NBD_INFO_BLOCK_SIZE to " "use this export"); } /* Final reply */ - rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt, errp); + rc = nbd_negotiate_send_rep(client, NBD_REP_ACK, errp); if (rc < 0) { return rc; } - if (opt == NBD_OPT_GO) { + if (client->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); } @@ -533,11 +576,12 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, QIOChannelTLS *tioc; struct NBDTLSHandshakeData data = { 0 }; + assert(client->opt == NBD_OPT_STARTTLS); + trace_nbd_negotiate_handle_starttls(); ioc = client->ioc; - if (nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, - NBD_OPT_STARTTLS, errp) < 0) { + if (nbd_negotiate_send_rep(client, NBD_REP_ACK, errp) < 0) { return NULL; } @@ -570,34 +614,6 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, return QIO_CHANNEL(tioc); } -/* nbd_reject_length: Handle any unexpected payload. - * @fatal requests that we quit talking to the client, even if we are able - * to successfully send an error to the guest. - * Return: - * -errno transmission error occurred or @fatal was requested, errp is set - * 0 error message successfully sent to client, errp is not set - */ -static int nbd_reject_length(NBDClient *client, uint32_t length, - uint32_t option, bool fatal, Error **errp) -{ - int ret; - - assert(length); - if (nbd_drop(client->ioc, length, errp) < 0) { - return -EIO; - } - ret = nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, - option, errp, - "option '%s' should have zero length", - nbd_opt_lookup(option)); - if (fatal && !ret) { - error_setg(errp, "option '%s' should have zero length", - nbd_opt_lookup(option)); - return -EINVAL; - } - return ret; -} - /* nbd_negotiate_options * Process all NBD_OPT_* client option commands, during fixed newstyle * negotiation. @@ -670,12 +686,15 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, return -EINVAL; } option = be32_to_cpu(option); + client->opt = option; if (nbd_read(client->ioc, &length, sizeof(length), errp) < 0) { error_prepend(errp, "read failed: "); return -EINVAL; } length = be32_to_cpu(length); + assert(!client->optlen); + client->optlen = length; if (length > NBD_MAX_BUFFER_SIZE) { error_setg(errp, "len (%" PRIu32" ) is larger than max len (%u)", @@ -697,8 +716,7 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, if (length) { /* Unconditionally drop the connection if the client * can't start a TLS negotiation correctly */ - return nbd_reject_length(client, length, option, true, - errp); + return nbd_reject_length(client, true, errp); } tioc = nbd_negotiate_handle_starttls(client, errp); if (!tioc) { @@ -716,15 +734,9 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, return -EINVAL; default: - if (nbd_drop(client->ioc, length, errp) < 0) { - return -EIO; - } - ret = nbd_negotiate_send_rep_err(client->ioc, - NBD_REP_ERR_TLS_REQD, - option, errp, - "Option 0x%" PRIx32 - "not permitted before TLS", - option); + ret = nbd_opt_drop(client, NBD_REP_ERR_TLS_REQD, errp, + "Option 0x%" PRIx32 + "not permitted before TLS", option); /* 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. */ @@ -737,8 +749,7 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, switch (option) { case NBD_OPT_LIST: if (length) { - ret = nbd_reject_length(client, length, option, false, - errp); + ret = nbd_reject_length(client, false, errp); } else { ret = nbd_negotiate_handle_list(client, errp); } @@ -748,18 +759,17 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, /* 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, NULL); + nbd_negotiate_send_rep(client, NBD_REP_ACK, NULL); return 1; case NBD_OPT_EXPORT_NAME: - return nbd_negotiate_handle_export_name(client, length, + return nbd_negotiate_handle_export_name(client, myflags, no_zeroes, errp); case NBD_OPT_INFO: case NBD_OPT_GO: - ret = nbd_negotiate_handle_info(client, length, option, - myflags, errp); + ret = nbd_negotiate_handle_info(client, myflags, errp); if (ret == 1) { assert(option == NBD_OPT_GO); return 0; @@ -768,47 +778,36 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, case NBD_OPT_STARTTLS: if (length) { - ret = nbd_reject_length(client, length, option, false, - errp); + ret = nbd_reject_length(client, false, errp); } else if (client->tlscreds) { - ret = nbd_negotiate_send_rep_err(client->ioc, - NBD_REP_ERR_INVALID, - option, errp, + ret = nbd_negotiate_send_rep_err(client, + NBD_REP_ERR_INVALID, errp, "TLS already enabled"); } else { - ret = nbd_negotiate_send_rep_err(client->ioc, - NBD_REP_ERR_POLICY, - option, errp, + ret = nbd_negotiate_send_rep_err(client, + NBD_REP_ERR_POLICY, errp, "TLS not configured"); } break; case NBD_OPT_STRUCTURED_REPLY: if (length) { - ret = nbd_reject_length(client, length, option, false, - errp); + ret = nbd_reject_length(client, false, errp); } else if (client->structured_reply) { ret = nbd_negotiate_send_rep_err( - client->ioc, NBD_REP_ERR_INVALID, option, errp, + client, NBD_REP_ERR_INVALID, errp, "structured reply already negotiated"); } else { - ret = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, - option, errp); + ret = nbd_negotiate_send_rep(client, NBD_REP_ACK, errp); client->structured_reply = true; myflags |= NBD_FLAG_SEND_DF; } break; default: - if (nbd_drop(client->ioc, length, errp) < 0) { - return -EIO; - } - ret = nbd_negotiate_send_rep_err(client->ioc, - NBD_REP_ERR_UNSUP, - option, errp, - "Unsupported option 0x%" - PRIx32 " (%s)", option, - nbd_opt_lookup(option)); + ret = nbd_opt_drop(client, NBD_REP_ERR_UNSUP, errp, + "Unsupported option 0x%" PRIx32 " (%s)", + option, nbd_opt_lookup(option)); break; } } else { @@ -818,7 +817,7 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, */ switch (option) { case NBD_OPT_EXPORT_NAME: - return nbd_negotiate_handle_export_name(client, length, + return nbd_negotiate_handle_export_name(client, myflags, no_zeroes, errp); @@ -898,6 +897,7 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) } } + assert(!client->optlen); trace_nbd_negotiate_success(); return 0; |