Implement "list exports" message
authorWouter Verhelst <w@uter.be>
Sat, 5 May 2012 09:04:39 +0000 (11:04 +0200)
committerWouter Verhelst <w@uter.be>
Sat, 5 May 2012 09:04:39 +0000 (11:04 +0200)
Previously, there was no way for the client to get a list of all
available exports from an nbd-server. This implements that.

Additionally, it implements a way for the server to understand more
options than just the one "NBD_OPT_EXPORT_NAME" we did before, and
proper handling of "sorry, I don't know what you're talking about".

cliserv.h
doc/proto.txt
nbd-client.c
nbd-server.c

index f2c1b3712c3697e4979f203dbfb12639d4c364b5..729e139e5b7172e0e7521095ce9f0c0218bf093d 100644 (file)
--- a/cliserv.h
+++ b/cliserv.h
@@ -56,6 +56,7 @@ typedef unsigned long long u64;
 
 u64 cliserv_magic = 0x00420281861253LL;
 u64 opts_magic = 0x49484156454F5054LL;
+u64 rep_magic = 0x3e889045565a9LL;
 #define INIT_PASSWD "NBDMAGIC"
 
 #define INFO(a) do { } while(0)
@@ -148,4 +149,20 @@ u64 ntohll(u64 a) {
                                         * served */
 
 /* Options that the client can select to the server */
-#define NBD_OPT_EXPORT_NAME    (1 << 0)        /* Client wants to select a named export (is followed by length and name of export) */
+#define NBD_OPT_EXPORT_NAME    (1)     /** Client wants to select a named export (is followed by name of export) */
+#define NBD_OPT_ABORT          (2)     /** Client wishes to abort negotiation */
+#define NBD_OPT_LIST           (3)     /** Client request list of supported exports (not followed by data) */
+
+/* Replies the server can send during negotiation */
+#define NBD_REP_ACK            (1)     /** ACK a request. Data: option number to be acked */
+#define NBD_REP_SERVER         (2)     /** Reply to NBD_OPT_LIST (one of these per server; must be followed by NBD_REP_ACK to signal the end of the list */
+#define NBD_REP_FLAG_ERROR     (1 << 31)       /** If the high bit is set, the reply is an error */
+#define NBD_REP_ERR_UNSUP      (1 | NBD_REP_FLAG_ERROR)        /** Client requested an option not understood by this version of the server */
+#define NBD_REP_ERR_POLICY     (2 | NBD_REP_FLAG_ERROR)        /** Client requested an option not allowed by server configuration. (e.g., the option was disabled) */
+#define NBD_REP_ERR_INVALID    (3 | NBD_REP_FLAG_ERROR)        /** Client issued an invalid request */
+#define NBD_REP_ERR_PLATFORM   (4 | NBD_REP_FLAG_ERROR)        /** Option not supported on this platform */
+
+/* Global flags */
+#define NBD_FLAG_FIXED_NEWSTYLE (1 << 0)       /* new-style export that actually supports extending */
+/* Flags from client to server. Only one such option currently. */
+#define NBD_FLAG_C_FIXED_NEWSTYLE NBD_FLAG_FIXED_NEWSTYLE
index c1151578630fc1e0567867ebe85bd92b39cf0073..320111f4d07b756c9df63bff267dc252d75bdfe3 100644 (file)
@@ -107,7 +107,8 @@ production purposes.
 
 S: "NBDMAGIC" (as in the old style handshake)
 S: 0x49484156454F5054 (note different magic number)
-S: 16 bits of zero (reserved for future use)
+S: 16 bits of zero (bits 1-15 reserved for future use; bit 0 in use to
+   signal fixed newstyle (see below))
 C: 32 bits of zero (reserved for future use)
 
 This completes the initial phase of negotiation; the client and server
@@ -123,11 +124,9 @@ The generic format of setting an option is as follows:
 
 C: 0x49484156454F5054 (note same new-style handshake's magic number)
 C: 32 bits denoting the chosen option (NBD_OPT_EXPORT_NAME is the only
-   possible value currently)
+   possible value in this version of the protocol)
 C: unsigned 32 bit length of option data
 C: (any data needed for the chosen option)
-S: (any response as needed and defined by the chosen option; currently
-   this does not happen).
 
 The presence of the option length in every option allows the server
 to skip any options presented by the client that it does not
@@ -156,25 +155,143 @@ signalling that an extra flag field will follow, to which the client
 will have to reply with a flag field of its own before the extra flags
 are sent. This is not yet implemented.
 
-Flag bits
----------
-
-bit 0 - NBD_FLAG_HAS_FLAGS
-should always be 1
-
-bit 1 - NBD_FLAG_READ_ONLY
-should be set to 1 if the export is read-only
-
-bit 2 - NBD_FLAG_SEND_FLUSH
-should be set to 1 if the server supports NBD_CMD_FLUSH commands
-
-bit 3 - NBD_FLAG_SEND_FUA
-should be set to 1 if the server supports the NBD_CMD_FLAG_FUA flag
-
-bit 4 - NBD_FLAG_ROTATIONAL
-should be set to 1 to let the client schedule I/O accesses as for a
-rotational medium
-
-bit 5 - NBD_FLAG_SEND_TRIM
-should be set to 1 if the server supports NBD_CMD_TRIM commands
+Fixed 'new' style handshake
+---------------------------
+
+Unfortunately, due to a mistake on my end, the server would immediately
+close the connection when it saw an option it did not understand, rather
+than signalling this fact to the client, which would've allowed it to
+retry; and replies from the server were not structured either, which
+meant that if the server were to send something the client did not
+understand, it would have to abort negotiation as well.
+
+To fix these two issues, the handshake has been extended once more:
+
+- The server will set bit 0 of its first set of reserved flags, to
+  signal that it supports this version of the protocol.
+- The client should reply with bit 0 set in its reserved field too,
+  though its side of the protocol does not change incompatibly.
+- The client may now send other options to the server as appropriate, in
+  the generic format for sending an option as described above.
+- The server will reply to any option apart from NBD_OPT_EXPORT_NAME
+  with reply packets in the following format:
+
+S: 64 bits, 0x3e889045565a9 (magic number for replies)
+S: 32 bits, the option as sent by the client to which this is a reply
+   packet.
+S: 32 bits, denoting reply type (e.g., NBD_REP_ACK to denote successful
+   completion, or NBD_REP_ERR_UNSUP to denote use of an option not known
+   by this server
+S: 32 bits, length of the reply. This may be zero for some replies, in
+   which case the next field is not sent
+S: any data as required by the reply (e.g., an export name in the case
+   of NBD_REP_SERVER
+
+As there is no unique number for client requests, clients who want to
+differentiate between answers to two instances of the same option during
+any negotiation must make sure they've seen the answer to an outstanding
+request before sending the next one of the same type.
+
+Values
+------
+
+This section describes the meaning of constants (other than magic
+numbers) in the protocol handshake.
 
+Flag bits
+- - - - -
+
+* Per-export (16 bits, sent after option haggling, or immediately after
+  the global flag field in oldstyle negotiation):
+
+  bit 0 - NBD_FLAG_HAS_FLAGS
+  should always be 1
+
+  bit 1 - NBD_FLAG_READ_ONLY
+  should be set to 1 if the export is read-only
+
+  bit 2 - NBD_FLAG_SEND_FLUSH
+  should be set to 1 if the server supports NBD_CMD_FLUSH commands
+
+  bit 3 - NBD_FLAG_SEND_FUA
+  should be set to 1 if the server supports the NBD_CMD_FLAG_FUA flag
+
+  bit 4 - NBD_FLAG_ROTATIONAL
+  should be set to 1 to let the client schedule I/O accesses as for a
+  rotational medium
+
+  bit 5 - NBD_FLAG_SEND_TRIM
+  should be set to 1 if the server supports NBD_CMD_TRIM commands
+
+* Global flag bits (16 bits, after initial connection):
+
+  bit 0 - NBD_FLAG_FIXED_NEWSTYLE
+  should be set by servers that support the fixed newstyle protocol
+
+* Client (after initial connection and after receiving flags from
+  server):
+
+  bit 0 - NBD_FLAG_C_FIXED_NEWSTYLE
+  Should be set by clients that support the fixed newstyle protocol.
+  Servers may choose to honour fixed newstyle from clients that didn't
+  set this bit, but relying on this isn't recommended.
+
+Option types
+- - - - - - -
+
+* NBD_OPT_EXPORT_NAME (1)
+  Choose the export which the client would like to use, and end option
+  haggling. Data: name of the export, free-form UTF8 text (subject to
+  limitations by server implementation). If the chosen export does not
+  exist, the server closes the connection.
+
+* NBD_OPT_LIST (2)
+  Returns a number of NBD_REP_SERVER replies, one for each export,
+  followed by an NBD_REP_ACK.
+
+* NBD_OPT_ABORT (3)
+  Abort negotiation and close the connection. Optional.
+
+Reply types
+- - - - - -
+
+* NBD_REP_ACK (1)
+  Will be sent by the server when it accepts the option, or when sending
+  data related to the option (in the case of NBD_OPT_LIST) has finished.
+  No data.
+
+* NBD_REP_SERVER (2)
+  A description of an export. Data:
+  - 32 bits, length of name
+  - Name of the export, as expected by NBD_OPT_EXPORT_NAME
+  - If length of name < (length of reply as sent in the reply packet
+    header - 4), then the rest of the data contains some undefined
+    implementation-specific details about the export. This is not
+    currently implemented, but future versions of nbd-server may send
+    along some details about the export. If the client did not
+    explicitly request otherwise, these details are defined to be UTF-8
+    encoded data suitable for direct display to a human being.
+
+There are a number of error reply types, all of which are denoted by
+having bit 31 set. All error replies may have some data set, in which
+case that data is an error message suitable for display to the user.
+
+* NBD_REP_ERR_UNSUP (2^31 + 1)
+  The option sent by the client is unknown by this server
+  implementation (e.g., because the server is too old, or from another
+  source).
+
+* NBD_REP_ERR_POLICY (2^31 + 2)
+  The option sent by the client is known by this server and
+  syntactically valid, but server-side policy forbids the server to
+  allow the option (e.g., the client sent NBD_OPT_LIST but server
+  configuration has that disabled)
+
+* NBD_REP_ERR_INVALID (2^31 + 3)
+  The option sent by the client is know by this server, but was
+  determined by the server to be syntactically invalid. For instance,
+  the client sent an NBD_OPT_LIST with nonzero data length.
+
+* NBD_REP_ERR_PLATFORM (2^31 + 4)
+  The option sent by the client is not supported on the platform on
+  which the server is running. Not currently used.
index c69ea2cfa5fa570aa9d83c994fbf7740992a4cc5..30dc3e4f49299691131957ee91bac3526d264f60 100644 (file)
@@ -46,6 +46,8 @@
 #include <sdp_inet.h>
 #endif
 
+#define NBDC_DO_LIST 1
+
 int check_conn(char* devname, int do_print) {
        char buf[256];
        char* p;
@@ -124,7 +126,94 @@ int opennet(char *name, char* portstr, int sdp) {
        return sock;
 }
 
-void negotiate(int sock, u64 *rsize64, u32 *flags, char* name) {
+void ask_list(int sock) {
+       uint32_t opt;
+       uint32_t opt_server;
+       uint32_t len;
+       uint32_t reptype;
+       uint64_t magic;
+       char buf[1024];
+
+       magic = ntohll(opts_magic);
+       if (write(sock, &magic, sizeof(magic)) < 0)
+               err("Failed/2.2: %m");
+
+       /* Ask for the list */
+       opt = htonl(NBD_OPT_LIST);
+       if(write(sock, &opt, sizeof(opt)) < 0) {
+               err("writing list option failed: %m");
+       }
+       /* Send the length (zero) */
+       len = htonl(0);
+       if(write(sock, &len, sizeof(len)) < 0) {
+               err("writing length failed: %m");
+       }
+       do {
+               memset(buf, 0, 1024);
+               if(read(sock, &magic, sizeof(magic)) < 0) {
+                       err("Reading magic from server: %m");
+               }
+               if(read(sock, &opt_server, sizeof(opt_server)) < 0) {
+                       err("Reading option: %m");
+               }
+               if(read(sock, &reptype, sizeof(reptype)) <0) {
+                       err("Reading reply from server: %m");
+               }
+               if(read(sock, &len, sizeof(len)) < 0) {
+                       err("Reading length from server: %m");
+               }
+               magic=ntohll(magic);
+               len=ntohl(len);
+               reptype=ntohl(reptype);
+               if(magic != rep_magic) {
+                       err("Not enough magic from server");
+               }
+               if(reptype & NBD_REP_FLAG_ERROR) {
+                       switch(reptype) {
+                               case NBD_REP_ERR_POLICY:
+                                       fprintf(stderr, "\nE: listing not allowed by server.\n");
+                                       break;
+                               default:
+                                       fprintf(stderr, "\nE: unexpected error from server.\n");
+                                       break;
+                       }
+                       if(len) {
+                               if(read(sock, buf, len) < 0) {
+                                       fprintf(stderr, "\nE: could not read error message from server\n");
+                               }
+                               fprintf(stderr, "Server said: %s\n", buf);
+                       }
+                       exit(EXIT_FAILURE);
+               } else {
+                       if(len) {
+                               if(reptype != NBD_REP_SERVER) {
+                                       err("Server sent us a reply we don't understand!");
+                               }
+                               if(read(sock, &len, sizeof(len)) < 0) {
+                                       fprintf(stderr, "\nE: could not read export name length from server\n");
+                                       exit(EXIT_FAILURE);
+                               }
+                               len=ntohl(len);
+                               if(read(sock, buf, len) < 0) {
+                                       fprintf(stderr, "\nE: could not read export name from server\n");
+                                       exit(EXIT_FAILURE);
+                               }
+                               printf("%s\n", buf);
+                       }
+               }
+       } while(reptype != NBD_REP_ACK);
+       opt=htonl(NBD_OPT_ABORT);
+       len=htonl(0);
+       magic=htonll(opts_magic);
+       if (write(sock, &magic, sizeof(magic)) < 0)
+               err("Failed/2.2: %m");
+       if (write(sock, &opt, sizeof(opt)) < 0)
+               err("Failed writing abort");
+       if (write(sock, &len, sizeof(len)) < 0)
+               err("Failed writing length");
+}
+
+void negotiate(int sock, u64 *rsize64, u32 *flags, char* name, uint32_t needed_flags, uint32_t client_flags, uint32_t do_opts) {
        u64 magic, size64;
        uint16_t tmp;
        char buf[256] = "\0\0\0\0\0\0\0\0\0";
@@ -143,7 +232,6 @@ void negotiate(int sock, u64 *rsize64, u32 *flags, char* name) {
        if(name) {
                uint32_t opt;
                uint32_t namesize;
-               uint32_t reserved = 0;
 
                if (magic != opts_magic)
                        err("Not enough opts_magic");
@@ -152,15 +240,28 @@ void negotiate(int sock, u64 *rsize64, u32 *flags, char* name) {
                        err("Failed reading flags: %m");
                }
                *flags = ((u32)ntohs(tmp));
+               if((needed_flags & *flags) != needed_flags) {
+                       /* There's currently really only one reason why this
+                        * check could possibly fail, but we may need to change
+                        * this error message in the future... */
+                       fprintf(stderr, "\nE: Server does not support listing exports\n");
+                       exit(EXIT_FAILURE);
+               }
 
-               /* reserved for future use*/
-               if (write(sock, &reserved, sizeof(reserved)) < 0)
+               client_flags = htonl(client_flags);
+               if (write(sock, &client_flags, sizeof(client_flags)) < 0)
                        err("Failed/2.1: %m");
 
+               if(do_opts & NBDC_DO_LIST) {
+                       ask_list(sock);
+                       exit(EXIT_SUCCESS);
+               }
+
                /* Write the export name that we're after */
-               magic = ntohll(opts_magic);
+               magic = htonll(opts_magic);
                if (write(sock, &magic, sizeof(magic)) < 0)
                        err("Failed/2.2: %m");
+
                opt = ntohl(NBD_OPT_EXPORT_NAME);
                if (write(sock, &opt, sizeof(opt)) < 0)
                        err("Failed/2.3: %m");
@@ -283,6 +384,7 @@ void usage(char* errmsg, ...) {
        fprintf(stderr, "Or   : nbd-client -d nbd_device\n");
        fprintf(stderr, "Or   : nbd-client -c nbd_device\n");
        fprintf(stderr, "Or   : nbd-client -h|--help\n");
+       fprintf(stderr, "Or   : nbd-client -l|--list host\n");
        fprintf(stderr, "Default value for blocksize is 1024 (recommended for ethernet)\n");
        fprintf(stderr, "Allowed values for blocksize are 512,1024,2048,4096\n"); /* will be checked in kernel :) */
        fprintf(stderr, "Note, that kernel 2.4.2 and older ones do not work correctly with\n");
@@ -328,11 +430,15 @@ int main(int argc, char *argv[]) {
        int c;
        int nonspecial=0;
        char* name=NULL;
+       uint32_t needed_flags;
+       uint32_t cflags;
+       uint32_t opts;
        struct option long_options[] = {
                { "block-size", required_argument, NULL, 'b' },
                { "check", required_argument, NULL, 'c' },
                { "disconnect", required_argument, NULL, 'd' },
                { "help", no_argument, NULL, 'h' },
+               { "list", no_argument, NULL, 'l' },
                { "name", required_argument, NULL, 'N' },
                { "nofork", no_argument, NULL, 'n' },
                { "persist", no_argument, NULL, 'p' },
@@ -344,7 +450,7 @@ int main(int argc, char *argv[]) {
 
        logging();
 
-       while((c=getopt_long_only(argc, argv, "-b:c:d:hnN:pSst:", long_options, NULL))>=0) {
+       while((c=getopt_long_only(argc, argv, "-b:c:d:hlnN:pSst:", long_options, NULL))>=0) {
                switch(c) {
                case 1:
                        // non-option argument
@@ -399,6 +505,14 @@ int main(int argc, char *argv[]) {
                case 'h':
                        usage(NULL);
                        exit(EXIT_SUCCESS);
+               case 'l':
+                       needed_flags |= NBD_FLAG_FIXED_NEWSTYLE;
+                       cflags |= NBD_FLAG_C_FIXED_NEWSTYLE;
+                       opts |= NBDC_DO_LIST;
+                       name="";
+                       nbddev="";
+                       port = NBD_DEFAULT_PORT;
+                       break;
                case 'n':
                        nofork=1;
                        break;
@@ -432,13 +546,14 @@ int main(int argc, char *argv[]) {
                exit(EXIT_FAILURE);
        }
 
+       sock = opennet(hostname, port, sdp);
+
+       negotiate(sock, &size64, &flags, name, needed_flags, cflags, opts);
+
        nbd = open(nbddev, O_RDWR);
        if (nbd < 0)
          err("Cannot open NBD: %m\nPlease ensure the 'nbd' module is loaded.");
 
-       sock = opennet(hostname, port, sdp);
-
-       negotiate(sock, &size64, &flags, name);
        setsizes(nbd, size64, blocksize, flags);
        set_timeout(nbd, timeout);
        finish_sock(sock, nbd, swap);
@@ -489,7 +604,7 @@ int main(int argc, char *argv[]) {
                                        close(sock); close(nbd);
                                        sock = opennet(hostname, port, sdp);
                                        nbd = open(nbddev, O_RDWR);
-                                       negotiate(sock, &new_size, &new_flags, name);
+                                       negotiate(sock, &new_size, &new_flags, name, needed_flags, cflags, opts);
                                        if (size64 != new_size) {
                                                err("Size of the device changed. Bye");
                                        }
index fd44659dd632787c41788571f0da27bab964cd3c..bd8250a3c57f4a9be4ee3b87d16833e4e1959002 100644 (file)
@@ -170,9 +170,11 @@ int dontfork = 0;
 #define F_ROTATIONAL 512  /**< Whether server wants the client to implement the elevator algorithm */
 #define F_TEMPORARY 1024  /**< Whether the backing file is temporary and should be created then unlinked */
 #define F_TRIM 2048       /**< Whether server wants TRIM (discard) to be sent by the client */
+#define F_FIXED 4096     /**< Client supports fixed new-style protocol (and can thus send us extra options */
 
 /** Global flags: */
 #define F_OLDSTYLE 1     /**< Allow oldstyle (port-based) exports */
+#define F_LIST 2         /**< Allow clients to list the exports on a server */
 GHashTable *children;
 char pidfname[256]; /**< name of our PID file */
 char pidftemplate[256]; /**< template to be used for the filename of the PID file */
@@ -254,6 +256,7 @@ typedef struct {
        u32 *difmap;         /**< see comment on the global difmap for this one */
        gboolean modern;     /**< client was negotiated using modern negotiation protocol */
        int transactionlogfd;/**< fd for transaction log */
+       int clientfeats;     /**< Features supported by this client */
 } CLIENT;
 
 /**
@@ -873,6 +876,7 @@ GArray* parse_cfile(gchar* f, bool have_global, GError** e) {
                { "listenaddr", FALSE, PARAM_STRING,    &modern_listen, 0 },
                { "port",       FALSE, PARAM_STRING,    &modernport,    0 },
                { "includedir", FALSE, PARAM_STRING,    &cfdir,         0 },
+               { "allowlist",  FALSE, PARAM_BOOL,      &glob_flags,    F_LIST },
        };
        PARAM* p=gp;
        int p_size=sizeof(gp)/sizeof(PARAM);
@@ -1504,6 +1508,77 @@ int exptrim(struct nbd_request* req, CLIENT* client) {
        return 0;
 }
 
+static void send_reply(uint32_t opt, int net, uint32_t reply_type, size_t datasize, void* data) {
+       uint64_t magic = htonll(0x3e889045565a9LL);
+       reply_type = htonl(reply_type);
+       uint32_t datsize = htonl(datasize);
+       struct iovec v_data[] = {
+               { &magic, sizeof(magic) },
+               { &opt, sizeof(opt) },
+               { &reply_type, sizeof(reply_type) },
+               { &datsize, sizeof(datsize) },
+               { data, datasize },
+       };
+       writev(net, v_data, 5);
+}
+
+static CLIENT* handle_export_name(uint32_t opt, int net, GArray* servers, uint32_t cflags) {
+       uint32_t namelen;
+       char* name;
+       int i;
+
+       if (read(net, &namelen, sizeof(namelen)) < 0)
+               err("Negotiation failed/7: %m");
+       namelen = ntohl(namelen);
+       name = malloc(namelen+1);
+       name[namelen]=0;
+       if (read(net, name, namelen) < 0)
+               err("Negotiation failed/8: %m");
+       for(i=0; i<servers->len; i++) {
+               SERVER* serve = &(g_array_index(servers, SERVER, i));
+               if(!strcmp(serve->servename, name)) {
+                       CLIENT* client = g_new0(CLIENT, 1);
+                       client->server = serve;
+                       client->exportsize = OFFT_MAX;
+                       client->net = net;
+                       client->modern = TRUE;
+                       client->transactionlogfd = -1;
+                       client->clientfeats = cflags;
+                       free(name);
+                       return client;
+               }
+       }
+       free(name);
+       return NULL;
+}
+
+static void handle_list(uint32_t opt, int net, GArray* servers, uint32_t cflags) {
+       uint32_t len;
+       int i;
+       char buf[1024];
+       char *ptr = buf + sizeof(len);
+
+       if (read(net, &len, sizeof(len)) < 0)
+               err("Negotiation failed/8: %m");
+       len = ntohl(len);
+       if(len) {
+               send_reply(opt, net, NBD_REP_ERR_INVALID, 0, NULL);
+       }
+       if(!(glob_flags & F_LIST)) {
+               send_reply(opt, net, NBD_REP_ERR_POLICY, 0, NULL);
+               err_nonfatal("Client tried disallowed list option");
+               return;
+       }
+       for(i=0; i<servers->len; i++) {
+               SERVER* serve = &(g_array_index(servers, SERVER, i));
+               len = strlen(serve->servename);
+               memcpy(buf, &len, sizeof(len));
+               strcpy(ptr, serve->servename);
+               send_reply(opt, net, NBD_REP_SERVER, len+sizeof(len), buf);
+       }
+       send_reply(opt, net, NBD_REP_ACK, 0, NULL);
+}
+
 /**
  * Do the initial negotiation.
  *
@@ -1518,6 +1593,9 @@ CLIENT* negotiate(int net, CLIENT *client, GArray* servers, int phase) {
 
        memset(zeros, '\0', sizeof(zeros));
        assert(((phase & NEG_INIT) && (phase & NEG_MODERN)) || client);
+       if(phase & NEG_MODERN) {
+               smallflags |= NBD_FLAG_FIXED_NEWSTYLE;
+       }
        if(phase & NEG_INIT) {
                /* common */
                if (write(net, INIT_PASSWD, 8) < 0) {
@@ -1540,54 +1618,50 @@ CLIENT* negotiate(int net, CLIENT *client, GArray* servers, int phase) {
        }
        if ((phase & NEG_MODERN) && (phase & NEG_INIT)) {
                /* modern */
-               uint32_t reserved;
+               uint32_t cflags;
                uint32_t opt;
-               uint32_t namelen;
-               char* name;
-               int i;
 
                if(!servers)
                        err("programmer error");
+               smallflags = htons(smallflags);
                if (write(net, &smallflags, sizeof(uint16_t)) < 0)
-                       err("Negotiation failed/3: %m");
-               if (read(net, &reserved, sizeof(reserved)) < 0)
-                       err("Negotiation failed/4: %m");
-               if (read(net, &magic, sizeof(magic)) < 0)
-                       err("Negotiation failed/5: %m");
-               magic = ntohll(magic);
-               if(magic != opts_magic) {
-                       close(net);
-                       return NULL;
-               }
-               if (read(net, &opt, sizeof(opt)) < 0)
-                       err("Negotiation failed/6: %m");
-               opt = ntohl(opt);
-               if(opt != NBD_OPT_EXPORT_NAME) {
+                       err_nonfatal("Negotiation failed/3: %m");
+               if (read(net, &cflags, sizeof(cflags)) < 0)
+                       err_nonfatal("Negotiation failed/4: %m");
+               cflags = htonl(cflags);
+               do {
+                       if (read(net, &magic, sizeof(magic)) < 0)
+                               err_nonfatal("Negotiation failed/5: %m");
+                       magic = ntohll(magic);
+                       if(magic != opts_magic) {
+                               close(net);
+                               return NULL;
+                       }
+                       if (read(net, &opt, sizeof(opt)) < 0)
+                               err_nonfatal("Negotiation failed/6: %m");
+                       opt = ntohl(opt);
+                       switch(opt) {
+                       case NBD_OPT_EXPORT_NAME:
+                               // NBD_OPT_EXPORT_NAME must be the last
+                               // selected option, so return from here
+                               // if that is chosen.
+                               return handle_export_name(opt, net, servers, cflags);
+                               break;
+                       case NBD_OPT_LIST:
+                               handle_list(opt, net, servers, cflags);
+                               break;
+                       case NBD_OPT_ABORT:
+                               // handled below
+                               break;
+                       default:
+                               send_reply(opt, net, NBD_REP_ERR_UNSUP, 0, NULL);
+                               break;
+                       }
+               } while((opt != NBD_OPT_EXPORT_NAME) && (opt != NBD_OPT_ABORT));
+               if(opt == NBD_OPT_ABORT) {
                        close(net);
                        return NULL;
                }
-               if (read(net, &namelen, sizeof(namelen)) < 0)
-                       err("Negotiation failed/7: %m");
-               namelen = ntohl(namelen);
-               name = malloc(namelen+1);
-               name[namelen]=0;
-               if (read(net, name, namelen) < 0)
-                       err("Negotiation failed/8: %m");
-               for(i=0; i<servers->len; i++) {
-                       SERVER* serve = &(g_array_index(servers, SERVER, i));
-                       if(!strcmp(serve->servename, name)) {
-                               CLIENT* client = g_new0(CLIENT, 1);
-                               client->server = serve;
-                               client->exportsize = OFFT_MAX;
-                               client->net = net;
-                               client->modern = TRUE;
-                               client->transactionlogfd = -1;
-                               free(name);
-                               return client;
-                       }
-               }
-               free(name);
-               return NULL;
        }
        /* common */
        size_host = htonll((u64)(client->exportsize));