NBD_OPT_EXPORT_NAME is lousy: it requires us to close the connection
rather than report an error.  Upstream NBD recently added NBD_OPT_GO
as the improved version of the option that does what we want, along
with NBD_OPT_INFO that returns the same information but does not
transition to transmission phase.

Signed-off-by: Eric Blake <ebl...@redhat.com>

---
v3: revamp to match latest version of NBD protocol
---
 include/block/nbd.h |   7 +++
 nbd/nbd-internal.h  |   2 +
 nbd/server.c        | 136 ++++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 136 insertions(+), 9 deletions(-)

diff --git a/include/block/nbd.h b/include/block/nbd.h
index 3fa7996..05c0e48 100644
--- a/include/block/nbd.h
+++ b/include/block/nbd.h
@@ -86,14 +86,21 @@ typedef struct nbd_reply nbd_reply;

 #define NBD_REP_ACK             (1)             /* Data sending finished. */
 #define NBD_REP_SERVER          (2)             /* Export description. */
+#define NBD_REP_INFO            (3)             /* NBD_OPT_INFO/GO. */

 #define NBD_REP_ERR_UNSUP       NBD_REP_ERR(1)  /* Unknown option. */
 #define NBD_REP_ERR_POLICY      NBD_REP_ERR(2)  /* Server denied */
 #define NBD_REP_ERR_INVALID     NBD_REP_ERR(3)  /* Invalid length */
 #define NBD_REP_ERR_PLATFORM    NBD_REP_ERR(4)  /* Not compiled in */
 #define NBD_REP_ERR_TLS_REQD    NBD_REP_ERR(5)  /* TLS required */
+#define NBD_REP_ERR_UNKNOWN     NBD_REP_ERR(6)  /* Export unknown */
 #define NBD_REP_ERR_SHUTDOWN    NBD_REP_ERR(7)  /* Server shutting down */

+/* Info types, used during NBD_REP_INFO */
+#define NBD_INFO_EXPORT         0
+#define NBD_INFO_NAME           1
+#define NBD_INFO_DESCRIPTION    2
+
 /* Request flags, sent from client to server during transmission phase */
 #define NBD_CMD_FLAG_FUA        (1 << 0)

diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h
index 0d40b1f..c597bb8 100644
--- a/nbd/nbd-internal.h
+++ b/nbd/nbd-internal.h
@@ -80,6 +80,8 @@
 #define NBD_OPT_LIST            (3)
 #define NBD_OPT_PEEK_EXPORT     (4)
 #define NBD_OPT_STARTTLS        (5)
+#define NBD_OPT_INFO            (6)
+#define NBD_OPT_GO              (7)

 /* 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.
diff --git a/nbd/server.c b/nbd/server.c
index fa6a994..1edb5f3 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -273,6 +273,8 @@ static int nbd_negotiate_send_rep_list(QIOChannel *ioc, 
NBDExport *exp)
     return 0;
 }

+/* Send a sequence of replies to NBD_OPT_LIST.
+ * Return -errno to kill connection, 0 to continue negotiation. */
 static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length)
 {
     NBDExport *exp;
@@ -295,6 +297,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 to kill connection, 0 to end negotiation. */
 static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length)
 {
     int rc = -EINVAL;
@@ -330,6 +334,104 @@ fail:
 }


+/* Handle NBD_OPT_INFO and NBD_OPT_GO.
+ * Return -errno to kill connection, 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 type;
+    uint64_t size;
+    uint16_t flags;
+
+    /* Client sends:
+        [20 ..  xx]   export name (length bytes)
+     */
+    TRACE("Checking length");
+    if (length >= sizeof(name)) {
+        if (nbd_negotiate_drop_sync(client->ioc, length) != length) {
+            return -EIO;
+        }
+        return nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_INVALID, opt);
+    }
+    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(client->ioc, NBD_REP_ERR_UNKNOWN, opt);
+    }
+
+    if (exp->description) {
+        size_t len = strlen(exp->description);
+
+        rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt,
+                                        sizeof(type) + len);
+        if (rc < 0) {
+            return rc;
+        }
+        type = cpu_to_be16(NBD_INFO_DESCRIPTION);
+        if (nbd_negotiate_write(client->ioc, &type, sizeof(type)) !=
+            sizeof(type)) {
+            LOG("write failed");
+            return -EIO;
+        }
+        if (nbd_negotiate_write(client->ioc, &exp->description, len) != len) {
+            LOG("write failed");
+            return -EIO;
+        }
+    }
+
+    rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt,
+                                    sizeof(type) + sizeof(size) +
+                                    sizeof(flags));
+    if (rc < 0) {
+        return rc;
+    }
+
+    type = cpu_to_be16(NBD_INFO_EXPORT);
+    size = cpu_to_be64(exp->size);
+    flags = cpu_to_be16(exp->nbdflags | myflags);
+
+    if (nbd_negotiate_write(client->ioc, &type, sizeof(type)) !=
+        sizeof(type)) {
+        LOG("write failed");
+        return -EIO;
+    }
+    if (nbd_negotiate_write(client->ioc, &size, sizeof(size)) !=
+        sizeof(size)) {
+        LOG("write failed");
+        return -EIO;
+    }
+    if (nbd_negotiate_write(client->ioc, &flags, sizeof(flags)) !=
+        sizeof(flags)) {
+        LOG("write failed");
+        return -EIO;
+    }
+
+    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;
+}
+
+
 static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
                                                  uint32_t length)
 {
@@ -381,7 +483,10 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient 
*client,
 }


-static int nbd_negotiate_options(NBDClient *client)
+/* Loop over all client options, during fixed newstyle negotiation.
+ * Return -errno to kill connection, 0 on successful NBD_OPT_EXPORT_NAME,
+ * 1 on successful NBD_OPT_GO.  */
+static int nbd_negotiate_options(NBDClient *client, uint16_t myflags)
 {
     uint32_t flags;
     bool fixedNewstyle = false;
@@ -515,6 +620,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;
@@ -627,18 +742,21 @@ 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;
         }

-        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. */
+            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.5.5


Reply via email to