u64 cliserv_magic = 0x00420281861253LL;
u64 opts_magic = 0x49484156454F5054LL;
+u64 rep_magic = 0x3e889045565a9LL;
#define INIT_PASSWD "NBDMAGIC"
#define INFO(a) do { } while(0)
* 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
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
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
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.
#include <sdp_inet.h>
#endif
+#define NBDC_DO_LIST 1
+
int check_conn(char* devname, int do_print) {
char buf[256];
char* p;
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";
if(name) {
uint32_t opt;
uint32_t namesize;
- uint32_t reserved = 0;
if (magic != opts_magic)
err("Not enough opts_magic");
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");
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");
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' },
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
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;
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);
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");
}
#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 */
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;
/**
{ "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);
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.
*
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) {
}
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));