nbd: Implement NBD_OPT_GO on server
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 <eblake@redhat.com> Message-Id: <20170707203049.534-7-eblake@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
23e099c34c
commit
f37708f6b8
179
nbd/server.c
179
nbd/server.c
@ -141,6 +141,7 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type,
|
|||||||
trace_nbd_negotiate_send_rep_len(opt, nbd_opt_lookup(opt),
|
trace_nbd_negotiate_send_rep_len(opt, nbd_opt_lookup(opt),
|
||||||
type, nbd_rep_lookup(type), len);
|
type, nbd_rep_lookup(type), len);
|
||||||
|
|
||||||
|
assert(len < NBD_MAX_BUFFER_SIZE);
|
||||||
magic = cpu_to_be64(NBD_REP_MAGIC);
|
magic = cpu_to_be64(NBD_REP_MAGIC);
|
||||||
if (nbd_write(ioc, &magic, sizeof(magic), errp) < 0) {
|
if (nbd_write(ioc, &magic, sizeof(magic), errp) < 0) {
|
||||||
error_prepend(errp, "write failed (rep magic): ");
|
error_prepend(errp, "write failed (rep magic): ");
|
||||||
@ -275,6 +276,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, errp);
|
return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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,
|
static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length,
|
||||||
uint16_t myflags, bool no_zeroes,
|
uint16_t myflags, bool no_zeroes,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
@ -323,6 +326,162 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
trace_nbd_negotiate_send_info(info, nbd_info_lookup(info), length);
|
||||||
|
rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt,
|
||||||
|
sizeof(info) + length, errp);
|
||||||
|
if (rc < 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
cpu_to_be16s(&info);
|
||||||
|
if (nbd_write(client->ioc, &info, sizeof(info), errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
if (nbd_write(client->ioc, buf, length, errp) < 0) {
|
||||||
|
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,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
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:
|
||||||
|
4 bytes: L, name length (can be 0)
|
||||||
|
L bytes: export name
|
||||||
|
2 bytes: N, number of requests (can be 0)
|
||||||
|
N * 2 bytes: N requests
|
||||||
|
*/
|
||||||
|
if (length < sizeof(namelen) + sizeof(requests)) {
|
||||||
|
msg = "overall request too short";
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
if (nbd_read(client->ioc, &namelen, sizeof(namelen), errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
be32_to_cpus(&namelen);
|
||||||
|
length -= sizeof(namelen);
|
||||||
|
if (namelen > length - sizeof(requests) || (length - namelen) % 2) {
|
||||||
|
msg = "name length is incorrect";
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
if (nbd_read(client->ioc, name, namelen, errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
name[namelen] = '\0';
|
||||||
|
length -= namelen;
|
||||||
|
trace_nbd_negotiate_handle_export_name_request(name);
|
||||||
|
|
||||||
|
if (nbd_read(client->ioc, &requests, sizeof(requests), errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
be16_to_cpus(&requests);
|
||||||
|
length -= sizeof(requests);
|
||||||
|
trace_nbd_negotiate_handle_info_requests(requests);
|
||||||
|
if (requests != length / sizeof(request)) {
|
||||||
|
msg = "incorrect number of requests for overall length";
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
while (requests--) {
|
||||||
|
if (nbd_read(client->ioc, &request, sizeof(request), errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
be16_to_cpus(&request);
|
||||||
|
length -= sizeof(request);
|
||||||
|
trace_nbd_negotiate_handle_info_request(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exp = nbd_export_find(name);
|
||||||
|
if (!exp) {
|
||||||
|
return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN,
|
||||||
|
opt, errp, "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,
|
||||||
|
errp);
|
||||||
|
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, errp);
|
||||||
|
if (rc < 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send NBD_INFO_EXPORT always */
|
||||||
|
trace_nbd_negotiate_new_style_size_flags(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, errp);
|
||||||
|
if (rc < 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Final reply */
|
||||||
|
rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt, errp);
|
||||||
|
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_drop(client->ioc, length, errp) < 0) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, opt,
|
||||||
|
errp, "%s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the
|
/* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the
|
||||||
* new channel for all further (now-encrypted) communication. */
|
* new channel for all further (now-encrypted) communication. */
|
||||||
static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
||||||
@ -380,7 +539,8 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* nbd_negotiate_options
|
/* nbd_negotiate_options
|
||||||
* Process all NBD_OPT_* client option commands.
|
* Process all NBD_OPT_* client option commands, during fixed newstyle
|
||||||
|
* negotiation.
|
||||||
* Return:
|
* Return:
|
||||||
* -errno on error, errp is set
|
* -errno on error, errp is set
|
||||||
* 0 on successful negotiation, errp is not set
|
* 0 on successful negotiation, errp is not set
|
||||||
@ -397,7 +557,7 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
|
|||||||
/* Client sends:
|
/* Client sends:
|
||||||
[ 0 .. 3] client flags
|
[ 0 .. 3] client flags
|
||||||
|
|
||||||
Then we loop until NBD_OPT_EXPORT_NAME:
|
Then we loop until NBD_OPT_EXPORT_NAME or NBD_OPT_GO:
|
||||||
[ 0 .. 7] NBD_OPTS_MAGIC
|
[ 0 .. 7] NBD_OPTS_MAGIC
|
||||||
[ 8 .. 11] NBD option
|
[ 8 .. 11] NBD option
|
||||||
[12 .. 15] Data length
|
[12 .. 15] Data length
|
||||||
@ -524,6 +684,19 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
|
|||||||
myflags, no_zeroes,
|
myflags, no_zeroes,
|
||||||
errp);
|
errp);
|
||||||
|
|
||||||
|
case NBD_OPT_INFO:
|
||||||
|
case NBD_OPT_GO:
|
||||||
|
ret = nbd_negotiate_handle_info(client, length, option,
|
||||||
|
myflags, errp);
|
||||||
|
if (ret == 1) {
|
||||||
|
assert(option == NBD_OPT_GO);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case NBD_OPT_STARTTLS:
|
case NBD_OPT_STARTTLS:
|
||||||
if (nbd_drop(client->ioc, length, errp) < 0) {
|
if (nbd_drop(client->ioc, length, errp) < 0) {
|
||||||
return -EIO;
|
return -EIO;
|
||||||
@ -606,7 +779,7 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp)
|
|||||||
[ 0 .. 7] passwd ("NBDMAGIC")
|
[ 0 .. 7] passwd ("NBDMAGIC")
|
||||||
[ 8 .. 15] magic (NBD_OPTS_MAGIC)
|
[ 8 .. 15] magic (NBD_OPTS_MAGIC)
|
||||||
[16 .. 17] server flags (0)
|
[16 .. 17] server flags (0)
|
||||||
....options sent, ending in NBD_OPT_EXPORT_NAME....
|
....options sent, ending in NBD_OPT_EXPORT_NAME or NBD_OPT_GO....
|
||||||
*/
|
*/
|
||||||
|
|
||||||
qio_channel_set_blocking(client->ioc, false, NULL);
|
qio_channel_set_blocking(client->ioc, false, NULL);
|
||||||
|
@ -33,6 +33,9 @@ nbd_negotiate_send_rep_err(const char *msg) "sending error message \"%s\""
|
|||||||
nbd_negotiate_send_rep_list(const char *name, const char *desc) "Advertising export name '%s' description '%s'"
|
nbd_negotiate_send_rep_list(const char *name, const char *desc) "Advertising export name '%s' description '%s'"
|
||||||
nbd_negotiate_handle_export_name(void) "Checking length"
|
nbd_negotiate_handle_export_name(void) "Checking length"
|
||||||
nbd_negotiate_handle_export_name_request(const char *name) "Client requested export '%s'"
|
nbd_negotiate_handle_export_name_request(const char *name) "Client requested export '%s'"
|
||||||
|
nbd_negotiate_send_info(int info, const char *name, uint32_t length) "Sending NBD_REP_INFO type %d (%s) with remaining length %" PRIu32
|
||||||
|
nbd_negotiate_handle_info_requests(int requests) "Client requested %d items of info"
|
||||||
|
nbd_negotiate_handle_info_request(int request, const char *name) "Client requested info %d (%s)"
|
||||||
nbd_negotiate_handle_starttls(void) "Setting up TLS"
|
nbd_negotiate_handle_starttls(void) "Setting up TLS"
|
||||||
nbd_negotiate_handle_starttls_handshake(void) "Starting TLS handshake"
|
nbd_negotiate_handle_starttls_handshake(void) "Starting TLS handshake"
|
||||||
nbd_negotiate_options_flags(uint32_t flags) "Received client flags 0x%" PRIx32
|
nbd_negotiate_options_flags(uint32_t flags) "Received client flags 0x%" PRIx32
|
||||||
|
Loading…
Reference in New Issue
Block a user