diff mbox

[v3,40/44] nbd: Implement NBD_OPT_GO on client

Message ID 1461368452-10389-41-git-send-email-eblake@redhat.com
State New
Headers show

Commit Message

Eric Blake April 22, 2016, 11:40 p.m. UTC
NBD_OPT_EXPORT_NAME is lousy: it doesn't have any sane error
reporting.  Upstream NBD recently added NBD_OPT_GO as the
improved version of the option that does what we want: it
reports sane errors on failures (including when a server
requires TLS but does not have NBD_OPT_GO!), and on success
it provides at least as much info as NBD_OPT_EXPORT_NAME sends.

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

---
v3: revamp to match latest version of NBD protocol
---
 nbd/nbd-internal.h |   3 ++
 nbd/client.c       | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 121 insertions(+), 2 deletions(-)

Comments

Alex Bligh April 25, 2016, 10:31 a.m. UTC | #1
On 23 Apr 2016, at 00:40, Eric Blake <eblake@redhat.com> wrote:

> NBD_OPT_EXPORT_NAME is lousy: it doesn't have any sane error
> reporting.  Upstream NBD recently added NBD_OPT_GO as the
> improved version of the option that does what we want: it
> reports sane errors on failures (including when a server
> requires TLS but does not have NBD_OPT_GO!), and on success
> it provides at least as much info as NBD_OPT_EXPORT_NAME sends.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> 

Reviewed-by: Alex Bligh <alex@alex.org.uk>

> ---
> v3: revamp to match latest version of NBD protocol
> ---
> nbd/nbd-internal.h |   3 ++
> nbd/client.c       | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 121 insertions(+), 2 deletions(-)
> 
> diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h
> index c597bb8..1935102 100644
> --- a/nbd/nbd-internal.h
> +++ b/nbd/nbd-internal.h
> @@ -55,8 +55,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
> diff --git a/nbd/client.c b/nbd/client.c
> index 89fa2c3..dac4f29 100644
> --- a/nbd/client.c
> +++ b/nbd/client.c
> @@ -222,6 +222,11 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply,
>                    reply->option);
>         break;
> 
> +    case NBD_REP_ERR_UNKNOWN:
> +        error_setg(errp, "Requested export not available for option %" PRIx32,
> +                   reply->option);
> +        break;
> +
>     case NBD_REP_ERR_SHUTDOWN:
>         error_setg(errp, "Server shutting down before option %" PRIx32,
>                    reply->option);
> @@ -311,6 +316,103 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, Error **errp)
> }
> 
> 
> +/* 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 @size and @flags populated). */
> +static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
> +                      NbdExportInfo *info, Error **errp)
> +{
> +    nbd_opt_reply reply;
> +    uint32_t len;
> +    uint16_t type;
> +    int error;
> +
> +    /* 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("Attempting NBD_OPT_GO for export '%s'", wantname);
> +    if (nbd_send_option_request(ioc, NBD_OPT_GO, -1, wantname, errp) < 0) {
> +        return -1;
> +    }
> +
> +    TRACE("Reading export info");
> +    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");
> +                return -1;
> +            }
> +            if (!info->flags) {
> +                error_setg(errp, "broken server omitted NBD_INFO_EXPORT");
> +                return -1;
> +            }
> +            TRACE("export is good to go");
> +            return 1;
> +        }
> +        if (reply.type != NBD_REP_INFO) {
> +            error_setg(errp, "unexpected reply type %" PRIx32 ", expected %x",
> +                       reply.type, NBD_REP_INFO);
> +            return -1;
> +        }
> +        if (len < sizeof(type)) {
> +            error_setg(errp, "NBD_REP_INFO length %" PRIu32 " is too short",
> +                       len);
> +            return -1;
> +        }
> +        if (read_sync(ioc, &type, sizeof(type)) != sizeof(type)) {
> +            error_setg(errp, "failed to read info type");
> +            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);
> +                return -1;
> +            }
> +            if (read_sync(ioc, &info->size, sizeof(info->size)) !=
> +                sizeof(info->size)) {
> +                error_setg(errp, "failed to read info size");
> +                return -1;
> +            }
> +            be64_to_cpus(&info->size);
> +            if (read_sync(ioc, &info->flags, sizeof(info->flags)) !=
> +                sizeof(info->flags)) {
> +                error_setg(errp, "failed to read info flags");
> +                return -1;
> +            }
> +            be16_to_cpus(&info->flags);
> +            TRACE("Size is %" PRIu64 ", export flags %" PRIx16,
> +                  info->size, info->flags);
> +            break;
> +
> +        default:
> +            TRACE("ignoring unknown export info %" PRIu16, type);
> +            if (drop_sync(ioc, len) != len) {
> +                error_setg(errp, "Failed to read info payload");
> +                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,
> @@ -515,11 +617,25 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
>             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;
> -- 
> 2.5.5
> 
>
diff mbox

Patch

diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h
index c597bb8..1935102 100644
--- a/nbd/nbd-internal.h
+++ b/nbd/nbd-internal.h
@@ -55,8 +55,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
diff --git a/nbd/client.c b/nbd/client.c
index 89fa2c3..dac4f29 100644
--- a/nbd/client.c
+++ b/nbd/client.c
@@ -222,6 +222,11 @@  static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply,
                    reply->option);
         break;

+    case NBD_REP_ERR_UNKNOWN:
+        error_setg(errp, "Requested export not available for option %" PRIx32,
+                   reply->option);
+        break;
+
     case NBD_REP_ERR_SHUTDOWN:
         error_setg(errp, "Server shutting down before option %" PRIx32,
                    reply->option);
@@ -311,6 +316,103 @@  static int nbd_receive_list(QIOChannel *ioc, const char *want, Error **errp)
 }


+/* 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 @size and @flags populated). */
+static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
+                      NbdExportInfo *info, Error **errp)
+{
+    nbd_opt_reply reply;
+    uint32_t len;
+    uint16_t type;
+    int error;
+
+    /* 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("Attempting NBD_OPT_GO for export '%s'", wantname);
+    if (nbd_send_option_request(ioc, NBD_OPT_GO, -1, wantname, errp) < 0) {
+        return -1;
+    }
+
+    TRACE("Reading export info");
+    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");
+                return -1;
+            }
+            if (!info->flags) {
+                error_setg(errp, "broken server omitted NBD_INFO_EXPORT");
+                return -1;
+            }
+            TRACE("export is good to go");
+            return 1;
+        }
+        if (reply.type != NBD_REP_INFO) {
+            error_setg(errp, "unexpected reply type %" PRIx32 ", expected %x",
+                       reply.type, NBD_REP_INFO);
+            return -1;
+        }
+        if (len < sizeof(type)) {
+            error_setg(errp, "NBD_REP_INFO length %" PRIu32 " is too short",
+                       len);
+            return -1;
+        }
+        if (read_sync(ioc, &type, sizeof(type)) != sizeof(type)) {
+            error_setg(errp, "failed to read info type");
+            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);
+                return -1;
+            }
+            if (read_sync(ioc, &info->size, sizeof(info->size)) !=
+                sizeof(info->size)) {
+                error_setg(errp, "failed to read info size");
+                return -1;
+            }
+            be64_to_cpus(&info->size);
+            if (read_sync(ioc, &info->flags, sizeof(info->flags)) !=
+                sizeof(info->flags)) {
+                error_setg(errp, "failed to read info flags");
+                return -1;
+            }
+            be16_to_cpus(&info->flags);
+            TRACE("Size is %" PRIu64 ", export flags %" PRIx16,
+                  info->size, info->flags);
+            break;
+
+        default:
+            TRACE("ignoring unknown export info %" PRIu16, type);
+            if (drop_sync(ioc, len) != len) {
+                error_setg(errp, "Failed to read info payload");
+                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,
@@ -515,11 +617,25 @@  int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
             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;