[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-block] [PATCH v4 5/8] nbd: Implement NBD_OPT_GO on server
From: |
Eric Blake |
Subject: |
[Qemu-block] [PATCH v4 5/8] nbd: Implement NBD_OPT_GO on server |
Date: |
Mon, 20 Feb 2017 20:42:45 -0600 |
NBD_OPT_EXPORT_NAME is lousy: per the NBD protocol, any failure
requires us to close the connection rather than report an error.
Therefore, upstream NBD recently added NBD_OPT_GO as the improved
version of the option that does what we want [1], along with
NBD_OPT_INFO that returns the same information but does not
transition to transmission phase.
[1] https://github.com/NetworkBlockDevice/nbd/blob/extension-info/doc/proto.md
This is a first cut at the information types, and only passes the
same information already available through NBD_OPT_LIST and
NBD_OPT_EXPORT_NAME; items like NBD_INFO_BLOCK_SIZE (and thus any
use of NBD_REP_ERR_BLOCK_SIZE_REQD) are intentionally left for
later patches.
Signed-off-by: Eric Blake <address@hidden>
---
v4: revamp to another round of NBD protocol changes
v3: revamp to match latest version of NBD protocol
---
nbd/server.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 195 insertions(+), 13 deletions(-)
diff --git a/nbd/server.c b/nbd/server.c
index 767ca0f..3b1a4a5 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -209,6 +209,7 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc,
uint32_t type,
TRACE("Reply opt=%" PRIx32 " (%s), type=%" PRIx32 " (%s), len=%" PRIu32,
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_negotiate_write(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
LOG("write failed (rep magic)");
@@ -331,6 +332,8 @@ static int nbd_negotiate_handle_list(NBDClient *client,
uint32_t length)
return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST);
}
+/* 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)
{
int rc = -EINVAL;
@@ -365,6 +368,171 @@ fail:
return rc;
}
+/* 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)
+{
+ int rc;
+
+ TRACE("Sending NBD_REP_INFO type %" PRIu16 " (%s) with remaining length %"
+ PRIu32, info, nbd_info_lookup(info), length);
+ rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt,
+ sizeof(info) + length);
+ if (rc < 0) {
+ return rc;
+ }
+ cpu_to_be16s(&info);
+ if (nbd_negotiate_write(client->ioc, &info, sizeof(info)) !=
+ sizeof(info)) {
+ LOG("write failed");
+ return -EIO;
+ }
+ if (nbd_negotiate_write(client->ioc, buf, length) != length) {
+ LOG("write failed");
+ 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)
+{
+ int rc;
+ char name[NBD_MAX_NAME_SIZE + 1];
+ NBDExport *exp;
+ uint16_t requests;
+ uint16_t request;
+ uint32_t namelen;
+ bool sendname = false;
+ char buf[sizeof(uint64_t) + sizeof(uint16_t)];
+ const char *msg;
+
+ /* Client sends:
+ 2 bytes: N, number of requests (can be 0)
+ N * 2 bytes: N requests
+ 4 bytes: L, name length (can be 0)
+ L bytes: export name
+ */
+ if (length < sizeof(requests) + sizeof(namelen)) {
+ msg = "overall request too short";
+ goto invalid;
+ }
+ if (nbd_negotiate_read(client->ioc, &requests, sizeof(requests)) !=
+ sizeof(requests)) {
+ LOG("read failed");
+ return -EIO;
+ }
+ be16_to_cpus(&requests);
+ length -= sizeof(requests);
+ TRACE("Client requested %d items of info", requests);
+ if (requests > (length - sizeof(namelen)) / sizeof(request)) {
+ msg = "too many requests for overall length";
+ goto invalid;
+ }
+ while (requests--) {
+ if (nbd_negotiate_read(client->ioc, &request, sizeof(request)) !=
+ sizeof(request)) {
+ LOG("read failed");
+ return -EIO;
+ }
+ be16_to_cpus(&request);
+ length -= sizeof(request);
+ TRACE("Client requested info %d (%s)", request,
+ nbd_info_lookup(request));
+ /* For now, we only care about NBD_INFO_NAME; everything else
+ * is either a request we don't know or something we send
+ * regardless of request. */
+ if (request == NBD_INFO_NAME) {
+ sendname = true;
+ }
+ }
+
+ if (nbd_negotiate_read(client->ioc, &namelen, sizeof(namelen)) !=
+ sizeof(namelen)) {
+ LOG("read failed");
+ return -EIO;
+ }
+ be32_to_cpus(&namelen);
+ length -= sizeof(namelen);
+ TRACE("Client requested namelen %u", namelen);
+ if (length != namelen || namelen > sizeof(name)) {
+ msg = "name too long";
+ goto invalid;
+ }
+ if (nbd_negotiate_read(client->ioc, name, length) != length) {
+ LOG("read failed");
+ return -EIO;
+ }
+ name[length] = '\0';
+
+ TRACE("Client requested info on export '%s'", name);
+
+ exp = nbd_export_find(name);
+ if (!exp) {
+ return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN,
+ opt, "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);
+ 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);
+ if (rc < 0) {
+ return rc;
+ }
+ }
+
+ /* Send NBD_INFO_EXPORT always */
+ TRACE("advertising size %" PRIu64 " and flags %" PRIx16,
+ 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);
+ if (rc < 0) {
+ return rc;
+ }
+
+ /* Final reply */
+ rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt);
+ 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_negotiate_drop_sync(client->ioc, length) != length) {
+ return -EIO;
+ }
+ return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, opt,
+ "%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,
@@ -420,9 +588,10 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient
*client,
}
-/* Process all NBD_OPT_* client option commands.
- * Return -errno on error, 0 on success. */
-static int nbd_negotiate_options(NBDClient *client)
+/* Process all NBD_OPT_* client option commands, during fixed newstyle
+ * negotiation. Return -errno on error, 0 on successful NBD_OPT_EXPORT_NAME,
+ * and 1 on successful NBD_OPT_GO. */
+static int nbd_negotiate_options(NBDClient *client, uint16_t myflags)
{
uint32_t flags;
bool fixedNewstyle = false;
@@ -555,6 +724,16 @@ static int nbd_negotiate_options(NBDClient *client)
case NBD_OPT_EXPORT_NAME:
return nbd_negotiate_handle_export_name(client, length);
+ case NBD_OPT_INFO:
+ case NBD_OPT_GO:
+ ret = nbd_negotiate_handle_info(client, length, clientflags,
+ myflags);
+ if (ret) {
+ assert(ret < 0 || clientflags == NBD_OPT_GO);
+ return ret;
+ }
+ break;
+
case NBD_OPT_STARTTLS:
if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
return -EIO;
@@ -675,20 +854,23 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData
*data)
LOG("write failed");
goto fail;
}
- rc = nbd_negotiate_options(client);
- if (rc != 0) {
+ rc = nbd_negotiate_options(client, myflags);
+ if (rc < 0) {
LOG("option negotiation failed");
goto fail;
}
- TRACE("advertising size %" PRIu64 " and flags %x",
- 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;
- if (nbd_negotiate_write(client->ioc, buf + 18, len) != len) {
- LOG("write failed");
- goto fail;
+ if (!rc) {
+ /* If options ended with NBD_OPT_GO, we already sent this. */
+ TRACE("advertising size %" PRIu64 " and flags %x",
+ 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;
+ if (nbd_negotiate_write(client->ioc, buf + 18, len) != len) {
+ LOG("write failed");
+ goto fail;
+ }
}
}
--
2.9.3
- [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, (continued)
- [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Eric Blake, 2017/02/20
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Paolo Bonzini, 2017/02/22
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Eric Blake, 2017/02/22
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Paolo Bonzini, 2017/02/22
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Eric Blake, 2017/02/22
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Paolo Bonzini, 2017/02/23
- Re: [Qemu-block] [PATCH v4 7/8] nbd: Implement NBD_INFO_BLOCK_SIZE on server, Eric Blake, 2017/02/23
[Qemu-block] [PATCH v4 6/8] nbd: Implement NBD_OPT_GO on client, Eric Blake, 2017/02/20
[Qemu-block] [PATCH v4 4/8] nbd: Expose and debug more NBD constants, Eric Blake, 2017/02/20
[Qemu-block] [PATCH v4 5/8] nbd: Implement NBD_OPT_GO on server,
Eric Blake <=
[Qemu-block] [PATCH v4 8/8] nbd: Implement NBD_INFO_BLOCK_SIZE on client, Eric Blake, 2017/02/20