nbd patches for 2019-01-21

Add 'qemu-nbd --list' for probing a remote NBD server's advertisements.
 
 - Eric Blake: 0/21 nbd: add qemu-nbd --list
 -----BEGIN PGP SIGNATURE-----
 
 iQEcBAABCAAGBQJcRktLAAoJEKeha0olJ0NqY8cIAJqcHDAuk1GwcDE/NIAASY9E
 2PsaEL2pM65bEWUwsnVkFyRb4y4S4QX9VtAR/c9G6jR0YHuDvCCz7VGjiHh3eGso
 9AWKzuh5C4mayAN5dTOlbjryOx7hlcz8wDDjP5OQNqIlBvmWVAnLeh5Kkqlc7KDk
 f1SH8kJfqegDAhKDeDhi/HGlL1UVzMr5A2sR6I1wbb5IbBru5JtAKzgqAmdBH893
 lWZ9EDXqajwPCo8ASwZNyawmtmjx+VBeFjO2juA3qTWvf262roiB3XT5W/3n/CCb
 Er5CBwQQvSFBUfF9bRYDziyjwQwTXoIQJ6nuJLzPGx2cUpQbJ/svphoRQ6rBOjY=
 =gywW
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2019-01-21' into staging

nbd patches for 2019-01-21

Add 'qemu-nbd --list' for probing a remote NBD server's advertisements.

- Eric Blake: 0/21 nbd: add qemu-nbd --list

# gpg: Signature made Mon 21 Jan 2019 22:44:27 GMT
# gpg:                using RSA key A7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>"
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>"
# gpg:                 aka "[jpeg image of size 6874]"
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* remotes/ericb/tags/pull-nbd-2019-01-21: (21 commits)
  iotests: Enhance 223, 233 to cover 'qemu-nbd --list'
  nbd/client: Work around 3.0 bug for listing meta contexts
  qemu-nbd: Add --list option
  nbd/client: Add meta contexts to nbd_receive_export_list()
  nbd/client: Add nbd_receive_export_list()
  nbd/client: Refactor nbd_opt_go() to support NBD_OPT_INFO
  nbd/client: Pull out oldstyle size determination
  nbd/client: Split handshake into two functions
  nbd/client: Refactor return of nbd_receive_negotiate()
  nbd/client: Split out nbd_receive_one_meta_context()
  nbd/client: Split out nbd_send_meta_query()
  nbd/client: Change signature of nbd_negotiate_simple_meta_context()
  nbd/client: Move export name into NBDExportInfo
  nbd/client: Refactor nbd_receive_list()
  qemu-nbd: Avoid strtol open-coding
  nbd/server: Favor [u]int64_t over off_t
  nbd/server: Hoist length check to qmp_nbd_server_add
  qemu-nbd: Sanity check partition bounds
  qemu-nbd: Enhance man page
  maint: Allow for EXAMPLES in texi2pod
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-01-22 17:56:21 +00:00
commit 952bc8b3c2
14 changed files with 951 additions and 345 deletions

View File

@ -860,6 +860,8 @@ docs/interop/qemu-qmp-ref.dvi docs/interop/qemu-qmp-ref.html \
docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7: \ docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7: \
docs/interop/qemu-qmp-ref.texi docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-qmp-ref.texi docs/interop/qemu-qmp-qapi.texi
$(filter %.1 %.7 %.8,$(DOCS)): scripts/texi2pod.pl
# Reports/Analysis # Reports/Analysis
%/coverage-report.html: %/coverage-report.html:

View File

@ -249,11 +249,11 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client,
} }
context_id = payload_advance32(&payload); context_id = payload_advance32(&payload);
if (client->info.meta_base_allocation_id != context_id) { if (client->info.context_id != context_id) {
error_setg(errp, "Protocol error: unexpected context id %d for " error_setg(errp, "Protocol error: unexpected context id %d for "
"NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context " "NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context "
"id is %d", context_id, "id is %d", context_id,
client->info.meta_base_allocation_id); client->info.context_id);
return -EINVAL; return -EINVAL;
} }
@ -999,10 +999,11 @@ int nbd_client_init(BlockDriverState *bs,
client->info.structured_reply = true; client->info.structured_reply = true;
client->info.base_allocation = true; client->info.base_allocation = true;
client->info.x_dirty_bitmap = g_strdup(x_dirty_bitmap); client->info.x_dirty_bitmap = g_strdup(x_dirty_bitmap);
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export, client->info.name = g_strdup(export ?: "");
tlscreds, hostname, ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), tlscreds, hostname,
&client->ioc, &client->info, errp); &client->ioc, &client->info, errp);
g_free(client->info.x_dirty_bitmap); g_free(client->info.x_dirty_bitmap);
g_free(client->info.name);
if (ret < 0) { if (ret < 0) {
logout("Failed to negotiate with the NBD server\n"); logout("Failed to negotiate with the NBD server\n");
return ret; return ret;

View File

@ -146,6 +146,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
BlockDriverState *bs = NULL; BlockDriverState *bs = NULL;
BlockBackend *on_eject_blk; BlockBackend *on_eject_blk;
NBDExport *exp; NBDExport *exp;
int64_t len;
if (!nbd_server) { if (!nbd_server) {
error_setg(errp, "NBD server not running"); error_setg(errp, "NBD server not running");
@ -168,6 +169,13 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
return; return;
} }
len = bdrv_getlength(bs);
if (len < 0) {
error_setg_errno(errp, -len,
"Failed to determine the NBD export's length");
return;
}
if (!has_writable) { if (!has_writable) {
writable = false; writable = false;
} }
@ -175,7 +183,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
writable = false; writable = false;
} }
exp = nbd_export_new(bs, 0, -1, name, NULL, bitmap, exp = nbd_export_new(bs, 0, len, name, NULL, bitmap,
writable ? 0 : NBD_FLAG_READ_ONLY, writable ? 0 : NBD_FLAG_READ_ONLY,
NULL, false, on_eject_blk, errp); NULL, false, on_eject_blk, errp);
if (!exp) { if (!exp) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016-2017 Red Hat, Inc. * Copyright (C) 2016-2019 Red Hat, Inc.
* Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws>
* *
* Network Block Device * Network Block Device
@ -263,25 +263,38 @@ struct NBDExportInfo {
bool request_sizes; bool request_sizes;
char *x_dirty_bitmap; char *x_dirty_bitmap;
/* Set by client before nbd_receive_negotiate(), or by server results
* during nbd_receive_export_list() */
char *name; /* must be non-NULL */
/* In-out fields, set by client before nbd_receive_negotiate() and /* In-out fields, set by client before nbd_receive_negotiate() and
* updated by server results during nbd_receive_negotiate() */ * updated by server results during nbd_receive_negotiate() */
bool structured_reply; bool structured_reply;
bool base_allocation; /* base:allocation context for NBD_CMD_BLOCK_STATUS */ bool base_allocation; /* base:allocation context for NBD_CMD_BLOCK_STATUS */
/* Set by server results during nbd_receive_negotiate() */ /* Set by server results during nbd_receive_negotiate() and
* nbd_receive_export_list() */
uint64_t size; uint64_t size;
uint16_t flags; uint16_t flags;
uint32_t min_block; uint32_t min_block;
uint32_t opt_block; uint32_t opt_block;
uint32_t max_block; uint32_t max_block;
uint32_t meta_base_allocation_id; uint32_t context_id;
/* Set by server results during nbd_receive_export_list() */
char *description;
int n_contexts;
char **contexts;
}; };
typedef struct NBDExportInfo NBDExportInfo; typedef struct NBDExportInfo NBDExportInfo;
int nbd_receive_negotiate(QIOChannel *ioc, const char *name, int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
QCryptoTLSCreds *tlscreds, const char *hostname, const char *hostname, QIOChannel **outioc,
QIOChannel **outioc, NBDExportInfo *info, NBDExportInfo *info, Error **errp);
void nbd_free_export_list(NBDExportInfo *info, int count);
int nbd_receive_export_list(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, NBDExportInfo **info,
Error **errp); Error **errp);
int nbd_init(int fd, QIOChannelSocket *sioc, NBDExportInfo *info, int nbd_init(int fd, QIOChannelSocket *sioc, NBDExportInfo *info,
Error **errp); Error **errp);
@ -294,8 +307,8 @@ int nbd_errno_to_system_errno(int err);
typedef struct NBDExport NBDExport; typedef struct NBDExport NBDExport;
typedef struct NBDClient NBDClient; typedef struct NBDClient NBDClient;
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size, NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
const char *name, const char *description, uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags, const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough, void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp); BlockBackend *on_eject_blk, Error **errp);

View File

@ -21,6 +21,7 @@
#include "qapi/error.h" #include "qapi/error.h"
#include "trace.h" #include "trace.h"
#include "nbd-internal.h" #include "nbd-internal.h"
#include "qemu/cutils.h"
/* Definitions for opaque data types */ /* Definitions for opaque data types */
@ -234,18 +235,24 @@ static int nbd_handle_reply_err(QIOChannel *ioc, NBDOptionReply *reply,
return result; return result;
} }
/* Process another portion of the NBD_OPT_LIST reply. Set *@match if /* nbd_receive_list:
* the current reply matches @want or if the server does not support * Process another portion of the NBD_OPT_LIST reply, populating any
* NBD_OPT_LIST, otherwise leave @match alone. Return 0 if iteration * name received into *@name. If @description is non-NULL, and the
* is complete, positive if more replies are expected, or negative * server provided a description, that is also populated. The caller
* with @errp set if an unrecoverable error occurred. */ * must eventually call g_free() on success.
static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match, * Returns 1 if name and description were set and iteration must continue,
* 0 if iteration is complete (including if OPT_LIST unsupported),
* -1 with @errp set if an unrecoverable error occurred.
*/
static int nbd_receive_list(QIOChannel *ioc, char **name, char **description,
Error **errp) Error **errp)
{ {
int ret = -1;
NBDOptionReply reply; NBDOptionReply reply;
uint32_t len; uint32_t len;
uint32_t namelen; uint32_t namelen;
char name[NBD_MAX_NAME_SIZE + 1]; char *local_name = NULL;
char *local_desc = NULL;
int error; int error;
if (nbd_receive_option_reply(ioc, NBD_OPT_LIST, &reply, errp) < 0) { if (nbd_receive_option_reply(ioc, NBD_OPT_LIST, &reply, errp) < 0) {
@ -253,9 +260,6 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
} }
error = nbd_handle_reply_err(ioc, &reply, errp); error = nbd_handle_reply_err(ioc, &reply, errp);
if (error <= 0) { if (error <= 0) {
/* The server did not support NBD_OPT_LIST, so set *match on
* the assumption that any name will be accepted. */
*match = true;
return error; return error;
} }
len = reply.length; len = reply.length;
@ -292,45 +296,54 @@ static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
return -1; return -1;
} }
if (namelen != strlen(want)) {
if (nbd_drop(ioc, len, errp) < 0) {
error_prepend(errp,
"failed to skip export name with wrong length: ");
nbd_send_opt_abort(ioc);
return -1;
}
return 1;
}
assert(namelen < sizeof(name)); local_name = g_malloc(namelen + 1);
if (nbd_read(ioc, name, namelen, errp) < 0) { if (nbd_read(ioc, local_name, namelen, errp) < 0) {
error_prepend(errp, "failed to read export name: "); error_prepend(errp, "failed to read export name: ");
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
return -1; goto out;
} }
name[namelen] = '\0'; local_name[namelen] = '\0';
len -= namelen; len -= namelen;
if (nbd_drop(ioc, len, errp) < 0) { if (len) {
local_desc = g_malloc(len + 1);
if (nbd_read(ioc, local_desc, len, errp) < 0) {
error_prepend(errp, "failed to read export description: "); error_prepend(errp, "failed to read export description: ");
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
return -1; goto out;
} }
if (!strcmp(name, want)) { local_desc[len] = '\0';
*match = true;
} }
return 1;
trace_nbd_receive_list(local_name, local_desc ?: "");
*name = local_name;
local_name = NULL;
if (description) {
*description = local_desc;
local_desc = NULL;
}
ret = 1;
out:
g_free(local_name);
g_free(local_desc);
return ret;
} }
/* 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_info_or_go:
* Send option for NBD_OPT_INFO or NBD_OPT_GO and parse the reply.
* Returns -1 if the option proves the export @info->name cannot be
* used, 0 if the option is unsupported (fall back to NBD_OPT_LIST and
* NBD_OPT_EXPORT_NAME in that case), and > 0 if the export is good to * NBD_OPT_EXPORT_NAME in that case), and > 0 if the export is good to
* go (with @info populated). */ * go (with the rest of @info populated).
static int nbd_opt_go(QIOChannel *ioc, const char *wantname, */
static int nbd_opt_info_or_go(QIOChannel *ioc, uint32_t opt,
NBDExportInfo *info, Error **errp) NBDExportInfo *info, Error **errp)
{ {
NBDOptionReply reply; NBDOptionReply reply;
uint32_t len = strlen(wantname); uint32_t len = strlen(info->name);
uint16_t type; uint16_t type;
int error; int error;
char *buf; char *buf;
@ -340,16 +353,17 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
* flags still 0 is a witness of a broken server. */ * flags still 0 is a witness of a broken server. */
info->flags = 0; info->flags = 0;
trace_nbd_opt_go_start(wantname); assert(opt == NBD_OPT_GO || opt == NBD_OPT_INFO);
trace_nbd_opt_info_go_start(nbd_opt_lookup(opt), info->name);
buf = g_malloc(4 + len + 2 + 2 * info->request_sizes + 1); buf = g_malloc(4 + len + 2 + 2 * info->request_sizes + 1);
stl_be_p(buf, len); stl_be_p(buf, len);
memcpy(buf + 4, wantname, len); memcpy(buf + 4, info->name, len);
/* At most one request, everything else up to server */ /* At most one request, everything else up to server */
stw_be_p(buf + 4 + len, info->request_sizes); stw_be_p(buf + 4 + len, info->request_sizes);
if (info->request_sizes) { if (info->request_sizes) {
stw_be_p(buf + 4 + len + 2, NBD_INFO_BLOCK_SIZE); stw_be_p(buf + 4 + len + 2, NBD_INFO_BLOCK_SIZE);
} }
error = nbd_send_option_request(ioc, NBD_OPT_GO, error = nbd_send_option_request(ioc, opt,
4 + len + 2 + 2 * info->request_sizes, 4 + len + 2 + 2 * info->request_sizes,
buf, errp); buf, errp);
g_free(buf); g_free(buf);
@ -358,7 +372,7 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
} }
while (1) { while (1) {
if (nbd_receive_option_reply(ioc, NBD_OPT_GO, &reply, errp) < 0) { if (nbd_receive_option_reply(ioc, opt, &reply, errp) < 0) {
return -1; return -1;
} }
error = nbd_handle_reply_err(ioc, &reply, errp); error = nbd_handle_reply_err(ioc, &reply, errp);
@ -368,8 +382,10 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
len = reply.length; len = reply.length;
if (reply.type == NBD_REP_ACK) { if (reply.type == NBD_REP_ACK) {
/* Server is done sending info and moved into transmission /*
phase, but make sure it sent flags */ * Server is done sending info, and moved into transmission
* phase for NBD_OPT_GO, but make sure it sent flags
*/
if (len) { if (len) {
error_setg(errp, "server sent invalid NBD_REP_ACK"); error_setg(errp, "server sent invalid NBD_REP_ACK");
return -1; return -1;
@ -378,7 +394,7 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
error_setg(errp, "broken server omitted NBD_INFO_EXPORT"); error_setg(errp, "broken server omitted NBD_INFO_EXPORT");
return -1; return -1;
} }
trace_nbd_opt_go_success(); trace_nbd_opt_info_go_success(nbd_opt_lookup(opt));
return 1; return 1;
} }
if (reply.type != NBD_REP_INFO) { if (reply.type != NBD_REP_INFO) {
@ -472,12 +488,12 @@ static int nbd_opt_go(QIOChannel *ioc, const char *wantname,
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
return -1; return -1;
} }
trace_nbd_opt_go_info_block_size(info->min_block, info->opt_block, trace_nbd_opt_info_block_size(info->min_block, info->opt_block,
info->max_block); info->max_block);
break; break;
default: default:
trace_nbd_opt_go_info_unknown(type, nbd_info_lookup(type)); trace_nbd_opt_info_unknown(type, nbd_info_lookup(type));
if (nbd_drop(ioc, len, errp) < 0) { if (nbd_drop(ioc, len, errp) < 0) {
error_prepend(errp, "Failed to read info payload: "); error_prepend(errp, "Failed to read info payload: ");
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
@ -493,7 +509,8 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
const char *wantname, const char *wantname,
Error **errp) Error **errp)
{ {
bool foundExport = false; bool list_empty = true;
bool found_export = false;
trace_nbd_receive_query_exports_start(wantname); trace_nbd_receive_query_exports_start(wantname);
if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) { if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
@ -501,14 +518,25 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
} }
while (1) { while (1) {
int ret = nbd_receive_list(ioc, wantname, &foundExport, errp); char *name;
int ret = nbd_receive_list(ioc, &name, NULL, errp);
if (ret < 0) { if (ret < 0) {
/* Server gave unexpected reply */ /* Server gave unexpected reply */
return -1; return -1;
} else if (ret == 0) { } else if (ret == 0) {
/* Done iterating. */ /* Done iterating. */
if (!foundExport) { if (list_empty) {
/*
* We don't have enough context to tell a server that
* sent an empty list apart from a server that does
* not support the list command; but as this function
* is just used to trigger a nicer error message
* before trying NBD_OPT_EXPORT_NAME, assume the
* export is available.
*/
return 0;
} else if (!found_export) {
error_setg(errp, "No export with name '%s' available", error_setg(errp, "No export with name '%s' available",
wantname); wantname);
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
@ -517,6 +545,11 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
trace_nbd_receive_query_exports_success(wantname); trace_nbd_receive_query_exports_success(wantname);
return 0; return 0;
} }
list_empty = false;
if (!strcmp(name, wantname)) {
found_export = true;
}
g_free(name);
} }
} }
@ -605,51 +638,67 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
return QIO_CHANNEL(tioc); return QIO_CHANNEL(tioc);
} }
/* nbd_negotiate_simple_meta_context: /*
* Set one meta context. Simple means that reply must contain zero (not * nbd_send_meta_query:
* negotiated) or one (negotiated) contexts. More contexts would be considered * Send 0 or 1 set/list meta context queries.
* as a protocol error. It's also implied that meta-data query equals queried * Return 0 on success, -1 with errp set for any error
* context name, so, if server replies with something different than @context,
* it is considered an error too.
* return 1 for successful negotiation, context_id is set
* 0 if operation is unsupported,
* -1 with errp set for any other error
*/ */
static int nbd_negotiate_simple_meta_context(QIOChannel *ioc, static int nbd_send_meta_query(QIOChannel *ioc, uint32_t opt,
const char *export, const char *export, const char *query,
const char *context, Error **errp)
uint32_t *context_id, {
int ret;
uint32_t export_len = strlen(export);
uint32_t queries = !!query;
uint32_t query_len = 0;
uint32_t data_len;
char *data;
char *p;
data_len = sizeof(export_len) + export_len + sizeof(queries);
if (query) {
query_len = strlen(query);
data_len += sizeof(query_len) + query_len;
} else {
assert(opt == NBD_OPT_LIST_META_CONTEXT);
}
p = data = g_malloc(data_len);
trace_nbd_opt_meta_request(nbd_opt_lookup(opt), query ?: "(all)", export);
stl_be_p(p, export_len);
memcpy(p += sizeof(export_len), export, export_len);
stl_be_p(p += export_len, queries);
if (query) {
stl_be_p(p += sizeof(queries), query_len);
memcpy(p += sizeof(query_len), query, query_len);
}
ret = nbd_send_option_request(ioc, opt, data_len, data, errp);
g_free(data);
return ret;
}
/*
* nbd_receive_one_meta_context:
* Called in a loop to receive and trace one set/list meta context reply.
* Pass non-NULL @name or @id to collect results back to the caller, which
* must eventually call g_free().
* return 1 if name is set and iteration must continue,
* 0 if iteration is complete (including if option is unsupported),
* -1 with errp set for any error
*/
static int nbd_receive_one_meta_context(QIOChannel *ioc,
uint32_t opt,
char **name,
uint32_t *id,
Error **errp) Error **errp)
{ {
int ret; int ret;
NBDOptionReply reply; NBDOptionReply reply;
uint32_t received_id = 0; char *local_name = NULL;
bool received = false; uint32_t local_id;
uint32_t export_len = strlen(export);
uint32_t context_len = strlen(context);
uint32_t data_len = sizeof(export_len) + export_len +
sizeof(uint32_t) + /* number of queries */
sizeof(context_len) + context_len;
char *data = g_malloc(data_len);
char *p = data;
trace_nbd_opt_meta_request(context, export); if (nbd_receive_option_reply(ioc, opt, &reply, errp) < 0) {
stl_be_p(p, export_len);
memcpy(p += sizeof(export_len), export, export_len);
stl_be_p(p += export_len, 1);
stl_be_p(p += sizeof(uint32_t), context_len);
memcpy(p += sizeof(context_len), context, context_len);
ret = nbd_send_option_request(ioc, NBD_OPT_SET_META_CONTEXT, data_len, data,
errp);
g_free(data);
if (ret < 0) {
return ret;
}
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
errp) < 0)
{
return -1; return -1;
} }
@ -658,29 +707,92 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
return ret; return ret;
} }
if (reply.type == NBD_REP_META_CONTEXT) { if (reply.type == NBD_REP_ACK) {
char *name; if (reply.length != 0) {
error_setg(errp, "Unexpected length to ACK response");
nbd_send_opt_abort(ioc);
return -1;
}
return 0;
} else if (reply.type != NBD_REP_META_CONTEXT) {
error_setg(errp, "Unexpected reply type %u (%s), expected %u (%s)",
reply.type, nbd_rep_lookup(reply.type),
NBD_REP_META_CONTEXT, nbd_rep_lookup(NBD_REP_META_CONTEXT));
nbd_send_opt_abort(ioc);
return -1;
}
if (reply.length != sizeof(received_id) + context_len) { if (reply.length <= sizeof(local_id) ||
error_setg(errp, "Failed to negotiate meta context '%s', server " reply.length > NBD_MAX_BUFFER_SIZE) {
"answered with unexpected length %" PRIu32, context, error_setg(errp, "Failed to negotiate meta context, server "
"answered with unexpected length %" PRIu32,
reply.length); reply.length);
nbd_send_opt_abort(ioc); nbd_send_opt_abort(ioc);
return -1; return -1;
} }
if (nbd_read(ioc, &received_id, sizeof(received_id), errp) < 0) { if (nbd_read(ioc, &local_id, sizeof(local_id), errp) < 0) {
return -1; return -1;
} }
received_id = be32_to_cpu(received_id); local_id = be32_to_cpu(local_id);
reply.length -= sizeof(received_id); reply.length -= sizeof(local_id);
name = g_malloc(reply.length + 1); local_name = g_malloc(reply.length + 1);
if (nbd_read(ioc, name, reply.length, errp) < 0) { if (nbd_read(ioc, local_name, reply.length, errp) < 0) {
g_free(name); g_free(local_name);
return -1; return -1;
} }
name[reply.length] = '\0'; local_name[reply.length] = '\0';
trace_nbd_opt_meta_reply(nbd_opt_lookup(opt), local_name, local_id);
if (name) {
*name = local_name;
} else {
g_free(local_name);
}
if (id) {
*id = local_id;
}
return 1;
}
/*
* nbd_negotiate_simple_meta_context:
* Request the server to set the meta context for export @info->name
* using @info->x_dirty_bitmap with a fallback to "base:allocation",
* setting @info->context_id to the resulting id. Fail if the server
* responds with more than one context or with a context different
* than the query.
* return 1 for successful negotiation,
* 0 if operation is unsupported,
* -1 with errp set for any other error
*/
static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
NBDExportInfo *info,
Error **errp)
{
/*
* TODO: Removing the x_dirty_bitmap hack will mean refactoring
* this function to request and store ids for multiple contexts
* (both base:allocation and a dirty bitmap), at which point this
* function should lose the term _simple.
*/
int ret;
const char *context = info->x_dirty_bitmap ?: "base:allocation";
bool received = false;
char *name = NULL;
if (nbd_send_meta_query(ioc, NBD_OPT_SET_META_CONTEXT,
info->name, context, errp) < 0) {
return -1;
}
ret = nbd_receive_one_meta_context(ioc, NBD_OPT_SET_META_CONTEXT,
&name, &info->context_id, errp);
if (ret < 0) {
return -1;
}
if (ret == 1) {
if (strcmp(context, name)) { if (strcmp(context, name)) {
error_setg(errp, "Failed to negotiate meta context '%s', server " error_setg(errp, "Failed to negotiate meta context '%s', server "
"answered with different context '%s'", context, "answered with different context '%s'", context,
@ -690,84 +802,115 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
return -1; return -1;
} }
g_free(name); g_free(name);
trace_nbd_opt_meta_reply(context, received_id);
received = true; received = true;
/* receive NBD_REP_ACK */ ret = nbd_receive_one_meta_context(ioc, NBD_OPT_SET_META_CONTEXT,
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply, NULL, NULL, errp);
errp) < 0) if (ret < 0) {
{ return -1;
}
}
if (ret != 0) {
error_setg(errp, "Server answered with more than one context");
nbd_send_opt_abort(ioc);
return -1;
}
return received;
}
/*
* nbd_list_meta_contexts:
* Request the server to list all meta contexts for export @info->name.
* return 0 if list is complete (even if empty),
* -1 with errp set for any error
*/
static int nbd_list_meta_contexts(QIOChannel *ioc,
NBDExportInfo *info,
Error **errp)
{
int ret;
int seen_any = false;
int seen_qemu = false;
if (nbd_send_meta_query(ioc, NBD_OPT_LIST_META_CONTEXT,
info->name, NULL, errp) < 0) {
return -1; return -1;
} }
ret = nbd_handle_reply_err(ioc, &reply, errp); while (1) {
char *context;
ret = nbd_receive_one_meta_context(ioc, NBD_OPT_LIST_META_CONTEXT,
&context, NULL, errp);
if (ret == 0 && seen_any && !seen_qemu) {
/*
* Work around qemu 3.0 bug: the server forgot to send
* "qemu:" replies to 0 queries. If we saw at least one
* reply (probably base:allocation), but none of them were
* qemu:, then run a more specific query to make sure.
*/
seen_qemu = true;
if (nbd_send_meta_query(ioc, NBD_OPT_LIST_META_CONTEXT,
info->name, "qemu:", errp) < 0) {
return -1;
}
continue;
}
if (ret <= 0) { if (ret <= 0) {
return ret; return ret;
} }
seen_any = true;
seen_qemu |= strstart(context, "qemu:", NULL);
info->contexts = g_renew(char *, info->contexts, ++info->n_contexts);
info->contexts[info->n_contexts - 1] = context;
} }
if (reply.type != NBD_REP_ACK) {
error_setg(errp, "Unexpected reply type %u (%s), expected %u (%s)",
reply.type, nbd_rep_lookup(reply.type),
NBD_REP_ACK, nbd_rep_lookup(NBD_REP_ACK));
nbd_send_opt_abort(ioc);
return -1;
}
if (reply.length) {
error_setg(errp, "Unexpected length to ACK response");
nbd_send_opt_abort(ioc);
return -1;
}
if (received) {
*context_id = received_id;
return 1;
}
return 0;
} }
int nbd_receive_negotiate(QIOChannel *ioc, const char *name, /*
QCryptoTLSCreds *tlscreds, const char *hostname, * nbd_start_negotiate:
QIOChannel **outioc, NBDExportInfo *info, * Start the handshake to the server. After a positive return, the server
* is ready to accept additional NBD_OPT requests.
* Returns: negative errno: failure talking to server
* 0: server is oldstyle, must call nbd_negotiate_finish_oldstyle
* 1: server is newstyle, but can only accept EXPORT_NAME
* 2: server is newstyle, but lacks structured replies
* 3: server is newstyle and set up for structured replies
*/
static int nbd_start_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, QIOChannel **outioc,
bool structured_reply, bool *zeroes,
Error **errp) Error **errp)
{ {
uint64_t magic; uint64_t magic;
int rc;
bool zeroes = true;
bool structured_reply = info->structured_reply;
bool base_allocation = info->base_allocation;
trace_nbd_receive_negotiate(tlscreds, hostname ? hostname : "<null>"); trace_nbd_start_negotiate(tlscreds, hostname ? hostname : "<null>");
info->structured_reply = false;
info->base_allocation = false;
rc = -EINVAL;
if (zeroes) {
*zeroes = true;
}
if (outioc) { if (outioc) {
*outioc = NULL; *outioc = NULL;
} }
if (tlscreds && !outioc) { if (tlscreds && !outioc) {
error_setg(errp, "Output I/O channel required for TLS"); error_setg(errp, "Output I/O channel required for TLS");
goto fail; return -EINVAL;
} }
if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) { if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) {
error_prepend(errp, "Failed to read initial magic: "); error_prepend(errp, "Failed to read initial magic: ");
goto fail; return -EINVAL;
} }
magic = be64_to_cpu(magic); magic = be64_to_cpu(magic);
trace_nbd_receive_negotiate_magic(magic); trace_nbd_receive_negotiate_magic(magic);
if (magic != NBD_INIT_MAGIC) { if (magic != NBD_INIT_MAGIC) {
error_setg(errp, "Bad initial magic received: 0x%" PRIx64, magic); error_setg(errp, "Bad initial magic received: 0x%" PRIx64, magic);
goto fail; return -EINVAL;
} }
if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) { if (nbd_read(ioc, &magic, sizeof(magic), errp) < 0) {
error_prepend(errp, "Failed to read server magic: "); error_prepend(errp, "Failed to read server magic: ");
goto fail; return -EINVAL;
} }
magic = be64_to_cpu(magic); magic = be64_to_cpu(magic);
trace_nbd_receive_negotiate_magic(magic); trace_nbd_receive_negotiate_magic(magic);
@ -779,7 +922,7 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
if (nbd_read(ioc, &globalflags, sizeof(globalflags), errp) < 0) { if (nbd_read(ioc, &globalflags, sizeof(globalflags), errp) < 0) {
error_prepend(errp, "Failed to read server flags: "); error_prepend(errp, "Failed to read server flags: ");
goto fail; return -EINVAL;
} }
globalflags = be16_to_cpu(globalflags); globalflags = be16_to_cpu(globalflags);
trace_nbd_receive_negotiate_server_flags(globalflags); trace_nbd_receive_negotiate_server_flags(globalflags);
@ -788,62 +931,132 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
clientflags |= NBD_FLAG_C_FIXED_NEWSTYLE; clientflags |= NBD_FLAG_C_FIXED_NEWSTYLE;
} }
if (globalflags & NBD_FLAG_NO_ZEROES) { if (globalflags & NBD_FLAG_NO_ZEROES) {
zeroes = false; if (zeroes) {
*zeroes = false;
}
clientflags |= NBD_FLAG_C_NO_ZEROES; clientflags |= NBD_FLAG_C_NO_ZEROES;
} }
/* client requested flags */ /* client requested flags */
clientflags = cpu_to_be32(clientflags); clientflags = cpu_to_be32(clientflags);
if (nbd_write(ioc, &clientflags, sizeof(clientflags), errp) < 0) { if (nbd_write(ioc, &clientflags, sizeof(clientflags), errp) < 0) {
error_prepend(errp, "Failed to send clientflags field: "); error_prepend(errp, "Failed to send clientflags field: ");
goto fail; return -EINVAL;
} }
if (tlscreds) { if (tlscreds) {
if (fixedNewStyle) { if (fixedNewStyle) {
*outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp); *outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp);
if (!*outioc) { if (!*outioc) {
goto fail; return -EINVAL;
} }
ioc = *outioc; ioc = *outioc;
} else { } else {
error_setg(errp, "Server does not support STARTTLS"); error_setg(errp, "Server does not support STARTTLS");
goto fail; return -EINVAL;
} }
} }
if (!name) {
trace_nbd_receive_negotiate_default_name();
name = "";
}
if (fixedNewStyle) { if (fixedNewStyle) {
int result; int result = 0;
if (structured_reply) { if (structured_reply) {
result = nbd_request_simple_option(ioc, result = nbd_request_simple_option(ioc,
NBD_OPT_STRUCTURED_REPLY, NBD_OPT_STRUCTURED_REPLY,
errp); errp);
if (result < 0) { if (result < 0) {
goto fail; return -EINVAL;
} }
info->structured_reply = result == 1; }
return 2 + result;
} else {
return 1;
}
} else if (magic == NBD_CLIENT_MAGIC) {
if (tlscreds) {
error_setg(errp, "Server does not support STARTTLS");
return -EINVAL;
}
return 0;
} else {
error_setg(errp, "Bad server magic received: 0x%" PRIx64, magic);
return -EINVAL;
}
}
/*
* nbd_negotiate_finish_oldstyle:
* Populate @info with the size and export flags from an oldstyle server,
* but does not consume 124 bytes of reserved zero padding.
* Returns 0 on success, -1 with @errp set on failure
*/
static int nbd_negotiate_finish_oldstyle(QIOChannel *ioc, NBDExportInfo *info,
Error **errp)
{
uint32_t oldflags;
if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
error_prepend(errp, "Failed to read export length: ");
return -EINVAL;
}
info->size = be64_to_cpu(info->size);
if (nbd_read(ioc, &oldflags, sizeof(oldflags), errp) < 0) {
error_prepend(errp, "Failed to read export flags: ");
return -EINVAL;
}
oldflags = be32_to_cpu(oldflags);
if (oldflags & ~0xffff) {
error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags);
return -EINVAL;
}
info->flags = oldflags;
return 0;
}
/*
* nbd_receive_negotiate:
* Connect to server, complete negotiation, and move into transmission phase.
* Returns: negative errno: failure talking to server
* 0: server is connected
*/
int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, QIOChannel **outioc,
NBDExportInfo *info, Error **errp)
{
int result;
bool zeroes;
bool base_allocation = info->base_allocation;
assert(info->name);
trace_nbd_receive_negotiate_name(info->name);
result = nbd_start_negotiate(ioc, tlscreds, hostname, outioc,
info->structured_reply, &zeroes, errp);
info->structured_reply = false;
info->base_allocation = false;
if (tlscreds && *outioc) {
ioc = *outioc;
} }
if (info->structured_reply && base_allocation) { switch (result) {
result = nbd_negotiate_simple_meta_context( case 3: /* newstyle, with structured replies */
ioc, name, info->x_dirty_bitmap ?: "base:allocation", info->structured_reply = true;
&info->meta_base_allocation_id, errp); if (base_allocation) {
result = nbd_negotiate_simple_meta_context(ioc, info, errp);
if (result < 0) { if (result < 0) {
goto fail; return -EINVAL;
} }
info->base_allocation = result == 1; info->base_allocation = result == 1;
} }
/* fall through */
case 2: /* newstyle, try OPT_GO */
/* Try NBD_OPT_GO first - if it works, we are done (it /* Try NBD_OPT_GO first - if it works, we are done (it
* also gives us a good message if the server requires * also gives us a good message if the server requires
* TLS). If it is not available, fall back to * TLS). If it is not available, fall back to
* NBD_OPT_LIST for nicer error messages about a missing * NBD_OPT_LIST for nicer error messages about a missing
* export, then use NBD_OPT_EXPORT_NAME. */ * export, then use NBD_OPT_EXPORT_NAME. */
result = nbd_opt_go(ioc, name, info, errp); result = nbd_opt_info_or_go(ioc, NBD_OPT_GO, info, errp);
if (result < 0) { if (result < 0) {
goto fail; return -EINVAL;
} }
if (result > 0) { if (result > 0) {
return 0; return 0;
@ -854,70 +1067,180 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
* query gives us better error reporting if the * query gives us better error reporting if the
* export name is not available. * export name is not available.
*/ */
if (nbd_receive_query_exports(ioc, name, errp) < 0) { if (nbd_receive_query_exports(ioc, info->name, errp) < 0) {
goto fail; return -EINVAL;
}
} }
/* fall through */
case 1: /* newstyle, but limited to EXPORT_NAME */
/* write the export name request */ /* write the export name request */
if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, name, if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, info->name,
errp) < 0) { errp) < 0) {
goto fail; return -EINVAL;
} }
/* Read the response */ /* Read the response */
if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) { if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) {
error_prepend(errp, "Failed to read export length: "); error_prepend(errp, "Failed to read export length: ");
goto fail; return -EINVAL;
} }
info->size = be64_to_cpu(info->size); info->size = be64_to_cpu(info->size);
if (nbd_read(ioc, &info->flags, sizeof(info->flags), errp) < 0) { if (nbd_read(ioc, &info->flags, sizeof(info->flags), errp) < 0) {
error_prepend(errp, "Failed to read export flags: "); error_prepend(errp, "Failed to read export flags: ");
goto fail; return -EINVAL;
} }
info->flags = be16_to_cpu(info->flags); info->flags = be16_to_cpu(info->flags);
} else if (magic == NBD_CLIENT_MAGIC) { break;
uint32_t oldflags; case 0: /* oldstyle, parse length and flags */
if (*info->name) {
if (name) { error_setg(errp, "Server does not support non-empty export names");
error_setg(errp, "Server does not support export names"); return -EINVAL;
goto fail;
} }
if (tlscreds) { if (nbd_negotiate_finish_oldstyle(ioc, info, errp) < 0) {
error_setg(errp, "Server does not support STARTTLS"); return -EINVAL;
goto fail;
} }
break;
if (nbd_read(ioc, &info->size, sizeof(info->size), errp) < 0) { default:
error_prepend(errp, "Failed to read export length: "); return result;
goto fail;
}
info->size = be64_to_cpu(info->size);
if (nbd_read(ioc, &oldflags, sizeof(oldflags), errp) < 0) {
error_prepend(errp, "Failed to read export flags: ");
goto fail;
}
oldflags = be32_to_cpu(oldflags);
if (oldflags & ~0xffff) {
error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags);
goto fail;
}
info->flags = oldflags;
} else {
error_setg(errp, "Bad server magic received: 0x%" PRIx64, magic);
goto fail;
} }
trace_nbd_receive_negotiate_size_flags(info->size, info->flags); trace_nbd_receive_negotiate_size_flags(info->size, info->flags);
if (zeroes && nbd_drop(ioc, 124, errp) < 0) { if (zeroes && nbd_drop(ioc, 124, errp) < 0) {
error_prepend(errp, "Failed to read reserved block: "); error_prepend(errp, "Failed to read reserved block: ");
goto fail; return -EINVAL;
} }
rc = 0; return 0;
}
fail: /* Clean up result of nbd_receive_export_list */
return rc; void nbd_free_export_list(NBDExportInfo *info, int count)
{
int i, j;
if (!info) {
return;
}
for (i = 0; i < count; i++) {
g_free(info[i].name);
g_free(info[i].description);
for (j = 0; j < info[i].n_contexts; j++) {
g_free(info[i].contexts[j]);
}
g_free(info[i].contexts);
}
g_free(info);
}
/*
* nbd_receive_export_list:
* Query details about a server's exports, then disconnect without
* going into transmission phase. Return a count of the exports listed
* in @info by the server, or -1 on error. Caller must free @info using
* nbd_free_export_list().
*/
int nbd_receive_export_list(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, NBDExportInfo **info,
Error **errp)
{
int result;
int count = 0;
int i;
int rc;
int ret = -1;
NBDExportInfo *array = NULL;
QIOChannel *sioc = NULL;
*info = NULL;
result = nbd_start_negotiate(ioc, tlscreds, hostname, &sioc, true, NULL,
errp);
if (tlscreds && sioc) {
ioc = sioc;
}
switch (result) {
case 2:
case 3:
/* newstyle - use NBD_OPT_LIST to populate array, then try
* NBD_OPT_INFO on each array member. If structured replies
* are enabled, also try NBD_OPT_LIST_META_CONTEXT. */
if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
goto out;
}
while (1) {
char *name;
char *desc;
rc = nbd_receive_list(ioc, &name, &desc, errp);
if (rc < 0) {
goto out;
} else if (rc == 0) {
break;
}
array = g_renew(NBDExportInfo, array, ++count);
memset(&array[count - 1], 0, sizeof(*array));
array[count - 1].name = name;
array[count - 1].description = desc;
array[count - 1].structured_reply = result == 3;
}
for (i = 0; i < count; i++) {
array[i].request_sizes = true;
rc = nbd_opt_info_or_go(ioc, NBD_OPT_INFO, &array[i], errp);
if (rc < 0) {
goto out;
} else if (rc == 0) {
/*
* Pointless to try rest of loop. If OPT_INFO doesn't work,
* it's unlikely that meta contexts work either
*/
break;
}
if (result == 3 &&
nbd_list_meta_contexts(ioc, &array[i], errp) < 0) {
goto out;
}
}
/* Send NBD_OPT_ABORT as a courtesy before hanging up */
nbd_send_opt_abort(ioc);
break;
case 1: /* newstyle, but limited to EXPORT_NAME */
error_setg(errp, "Server does not support export lists");
/* We can't even send NBD_OPT_ABORT, so merely hang up */
goto out;
case 0: /* oldstyle, parse length and flags */
array = g_new0(NBDExportInfo, 1);
array->name = g_strdup("");
count = 1;
if (nbd_negotiate_finish_oldstyle(ioc, array, errp) < 0) {
goto out;
}
/* Send NBD_CMD_DISC as a courtesy to the server, but ignore all
* errors now that we have the information we wanted. */
if (nbd_drop(ioc, 124, NULL) == 0) {
NBDRequest request = { .type = NBD_CMD_DISC };
nbd_send_request(ioc, &request);
}
break;
default:
goto out;
}
*info = array;
array = NULL;
ret = count;
out:
qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
qio_channel_close(ioc, NULL);
object_unref(OBJECT(sioc));
nbd_free_export_list(array, count);
return ret;
} }
#ifdef __linux__ #ifdef __linux__

View File

@ -77,8 +77,8 @@ struct NBDExport {
BlockBackend *blk; BlockBackend *blk;
char *name; char *name;
char *description; char *description;
off_t dev_offset; uint64_t dev_offset;
off_t size; uint64_t size;
uint16_t nbdflags; uint16_t nbdflags;
QTAILQ_HEAD(, NBDClient) clients; QTAILQ_HEAD(, NBDClient) clients;
QTAILQ_ENTRY(NBDExport) next; QTAILQ_ENTRY(NBDExport) next;
@ -1455,8 +1455,8 @@ static void nbd_eject_notifier(Notifier *n, void *data)
nbd_export_close(exp); nbd_export_close(exp);
} }
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size, NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
const char *name, const char *description, uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags, const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough, void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp) BlockBackend *on_eject_blk, Error **errp)
@ -1495,17 +1495,13 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
exp->refcount = 1; exp->refcount = 1;
QTAILQ_INIT(&exp->clients); QTAILQ_INIT(&exp->clients);
exp->blk = blk; exp->blk = blk;
assert(dev_offset <= INT64_MAX);
exp->dev_offset = dev_offset; exp->dev_offset = dev_offset;
exp->name = g_strdup(name); exp->name = g_strdup(name);
exp->description = g_strdup(description); exp->description = g_strdup(desc);
exp->nbdflags = nbdflags; exp->nbdflags = nbdflags;
exp->size = size < 0 ? blk_getlength(blk) : size; assert(size <= INT64_MAX - dev_offset);
if (exp->size < 0) { exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
error_setg_errno(errp, -exp->size,
"Failed to determine the NBD export's length");
goto fail;
}
exp->size -= exp->size % BDRV_SECTOR_SIZE;
if (bitmap) { if (bitmap) {
BdrvDirtyBitmap *bm = NULL; BdrvDirtyBitmap *bm = NULL;
@ -2134,10 +2130,10 @@ static int nbd_co_receive_request(NBDRequestData *req, NBDRequest *request,
return -EROFS; return -EROFS;
} }
if (request->from > client->exp->size || if (request->from > client->exp->size ||
request->from + request->len > client->exp->size) { request->len > client->exp->size - request->from) {
error_setg(errp, "operation past EOF; From: %" PRIu64 ", Len: %" PRIu32 error_setg(errp, "operation past EOF; From: %" PRIu64 ", Len: %" PRIu32
", Size: %" PRIu64, request->from, request->len, ", Size: %" PRIu64, request->from, request->len,
(uint64_t)client->exp->size); client->exp->size);
return (request->type == NBD_CMD_WRITE || return (request->type == NBD_CMD_WRITE ||
request->type == NBD_CMD_WRITE_ZEROES) ? -ENOSPC : -EINVAL; request->type == NBD_CMD_WRITE_ZEROES) ? -ENOSPC : -EINVAL;
} }

View File

@ -3,20 +3,21 @@ nbd_send_option_request(uint32_t opt, const char *name, uint32_t len) "Sending o
nbd_receive_option_reply(uint32_t option, const char *optname, uint32_t type, const char *typename, uint32_t length) "Received option reply %" PRIu32" (%s), type %" PRIu32" (%s), len %" PRIu32 nbd_receive_option_reply(uint32_t option, const char *optname, uint32_t type, const char *typename, uint32_t length) "Received option reply %" PRIu32" (%s), type %" PRIu32" (%s), len %" PRIu32
nbd_server_error_msg(uint32_t err, const char *type, const char *msg) "server reported error 0x%" PRIx32 " (%s) with additional message: %s" nbd_server_error_msg(uint32_t err, const char *type, const char *msg) "server reported error 0x%" PRIx32 " (%s) with additional message: %s"
nbd_reply_err_unsup(uint32_t option, const char *name) "server doesn't understand request %" PRIu32 " (%s), attempting fallback" nbd_reply_err_unsup(uint32_t option, const char *name) "server doesn't understand request %" PRIu32 " (%s), attempting fallback"
nbd_opt_go_start(const char *name) "Attempting NBD_OPT_GO for export '%s'" nbd_receive_list(const char *name, const char *desc) "export list includes '%s', description '%s'"
nbd_opt_go_success(void) "Export is good to go" nbd_opt_info_go_start(const char *opt, const char *name) "Attempting %s for export '%s'"
nbd_opt_go_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)" nbd_opt_info_go_success(const char *opt) "Export is ready after %s request"
nbd_opt_go_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "Block sizes are 0x%" PRIx32 ", 0x%" PRIx32 ", 0x%" PRIx32 nbd_opt_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)"
nbd_opt_info_block_size(uint32_t minimum, uint32_t preferred, uint32_t maximum) "Block sizes are 0x%" PRIx32 ", 0x%" PRIx32 ", 0x%" PRIx32
nbd_receive_query_exports_start(const char *wantname) "Querying export list for '%s'" nbd_receive_query_exports_start(const char *wantname) "Querying export list for '%s'"
nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'" nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'"
nbd_receive_starttls_new_client(void) "Setting up TLS" nbd_receive_starttls_new_client(void) "Setting up TLS"
nbd_receive_starttls_tls_handshake(void) "Starting TLS handshake" nbd_receive_starttls_tls_handshake(void) "Starting TLS handshake"
nbd_opt_meta_request(const char *context, const char *export) "Requesting to set meta context %s for export %s" nbd_opt_meta_request(const char *optname, const char *context, const char *export) "Requesting %s %s for export %s"
nbd_opt_meta_reply(const char *context, uint32_t id) "Received mapping of context %s to id %" PRIu32 nbd_opt_meta_reply(const char *optname, const char *context, uint32_t id) "Received %s mapping of %s to id %" PRIu32
nbd_receive_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%s" nbd_start_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%s"
nbd_receive_negotiate_magic(uint64_t magic) "Magic is 0x%" PRIx64 nbd_receive_negotiate_magic(uint64_t magic) "Magic is 0x%" PRIx64
nbd_receive_negotiate_server_flags(uint32_t globalflags) "Global flags are 0x%" PRIx32 nbd_receive_negotiate_server_flags(uint32_t globalflags) "Global flags are 0x%" PRIx32
nbd_receive_negotiate_default_name(void) "Using default NBD export name \"\"" nbd_receive_negotiate_name(const char *name) "Requesting NBD export name '%s'"
nbd_receive_negotiate_size_flags(uint64_t size, uint16_t flags) "Size is %" PRIu64 ", export flags 0x%" PRIx16 nbd_receive_negotiate_size_flags(uint64_t size, uint16_t flags) "Size is %" PRIu64 ", export flags 0x%" PRIx16
nbd_init_set_socket(void) "Setting NBD socket" nbd_init_set_socket(void) "Setting NBD socket"
nbd_init_set_block_size(unsigned long block_size) "Setting block size to %lu" nbd_init_set_block_size(unsigned long block_size) "Setting block size to %lu"

View File

@ -76,7 +76,8 @@ static void usage(const char *name)
{ {
(printf) ( (printf) (
"Usage: %s [OPTIONS] FILE\n" "Usage: %s [OPTIONS] FILE\n"
"QEMU Disk Network Block Device Server\n" " or: %s -L [OPTIONS]\n"
"QEMU Disk Network Block Device Utility\n"
"\n" "\n"
" -h, --help display this help and exit\n" " -h, --help display this help and exit\n"
" -V, --version output version information and exit\n" " -V, --version output version information and exit\n"
@ -98,6 +99,7 @@ static void usage(const char *name)
" -B, --bitmap=NAME expose a persistent dirty bitmap\n" " -B, --bitmap=NAME expose a persistent dirty bitmap\n"
"\n" "\n"
"General purpose options:\n" "General purpose options:\n"
" -L, --list list exports available from another NBD server\n"
" --object type,id=ID,... define an object such as 'secret' for providing\n" " --object type,id=ID,... define an object such as 'secret' for providing\n"
" passwords and/or encryption keys\n" " passwords and/or encryption keys\n"
" --tls-creds=ID use id of an earlier --object to provide TLS\n" " --tls-creds=ID use id of an earlier --object to provide TLS\n"
@ -131,7 +133,7 @@ static void usage(const char *name)
" --image-opts treat FILE as a full set of image options\n" " --image-opts treat FILE as a full set of image options\n"
"\n" "\n"
QEMU_HELP_BOTTOM "\n" QEMU_HELP_BOTTOM "\n"
, name, NBD_DEFAULT_PORT, "DEVICE"); , name, name, NBD_DEFAULT_PORT, "DEVICE");
} }
static void version(const char *name) static void version(const char *name)
@ -176,7 +178,7 @@ static void read_partition(uint8_t *p, struct partition_record *r)
} }
static int find_partition(BlockBackend *blk, int partition, static int find_partition(BlockBackend *blk, int partition,
off_t *offset, off_t *size) uint64_t *offset, uint64_t *size)
{ {
struct partition_record mbr[4]; struct partition_record mbr[4];
uint8_t data[MBR_SIZE]; uint8_t data[MBR_SIZE];
@ -243,6 +245,91 @@ static void termsig_handler(int signum)
} }
static int qemu_nbd_client_list(SocketAddress *saddr, QCryptoTLSCreds *tls,
const char *hostname)
{
int ret = EXIT_FAILURE;
int rc;
Error *err = NULL;
QIOChannelSocket *sioc;
NBDExportInfo *list;
int i, j;
sioc = qio_channel_socket_new();
if (qio_channel_socket_connect_sync(sioc, saddr, &err) < 0) {
error_report_err(err);
return EXIT_FAILURE;
}
rc = nbd_receive_export_list(QIO_CHANNEL(sioc), tls, hostname, &list,
&err);
if (rc < 0) {
if (err) {
error_report_err(err);
}
goto out;
}
printf("exports available: %d\n", rc);
for (i = 0; i < rc; i++) {
printf(" export: '%s'\n", list[i].name);
if (list[i].description && *list[i].description) {
printf(" description: %s\n", list[i].description);
}
if (list[i].flags & NBD_FLAG_HAS_FLAGS) {
printf(" size: %" PRIu64 "\n", list[i].size);
printf(" flags: 0x%x (", list[i].flags);
if (list[i].flags & NBD_FLAG_READ_ONLY) {
printf(" readonly");
}
if (list[i].flags & NBD_FLAG_SEND_FLUSH) {
printf(" flush");
}
if (list[i].flags & NBD_FLAG_SEND_FUA) {
printf(" fua");
}
if (list[i].flags & NBD_FLAG_ROTATIONAL) {
printf(" rotational");
}
if (list[i].flags & NBD_FLAG_SEND_TRIM) {
printf(" trim");
}
if (list[i].flags & NBD_FLAG_SEND_WRITE_ZEROES) {
printf(" zeroes");
}
if (list[i].flags & NBD_FLAG_SEND_DF) {
printf(" df");
}
if (list[i].flags & NBD_FLAG_CAN_MULTI_CONN) {
printf(" multi");
}
if (list[i].flags & NBD_FLAG_SEND_RESIZE) {
printf(" resize");
}
if (list[i].flags & NBD_FLAG_SEND_CACHE) {
printf(" cache");
}
printf(" )\n");
}
if (list[i].min_block) {
printf(" min block: %u\n", list[i].min_block);
printf(" opt block: %u\n", list[i].opt_block);
printf(" max block: %u\n", list[i].max_block);
}
if (list[i].n_contexts) {
printf(" available meta contexts: %d\n", list[i].n_contexts);
for (j = 0; j < list[i].n_contexts; j++) {
printf(" %s\n", list[i].contexts[j]);
}
}
}
nbd_free_export_list(list, rc);
ret = EXIT_SUCCESS;
out:
object_unref(OBJECT(sioc));
return ret;
}
#if HAVE_NBD_DEVICE #if HAVE_NBD_DEVICE
static void *show_parts(void *arg) static void *show_parts(void *arg)
{ {
@ -264,7 +351,7 @@ static void *show_parts(void *arg)
static void *nbd_client_thread(void *arg) static void *nbd_client_thread(void *arg)
{ {
char *device = arg; char *device = arg;
NBDExportInfo info = { .request_sizes = false, }; NBDExportInfo info = { .request_sizes = false, .name = g_strdup("") };
QIOChannelSocket *sioc; QIOChannelSocket *sioc;
int fd; int fd;
int ret; int ret;
@ -279,7 +366,7 @@ static void *nbd_client_thread(void *arg)
goto out; goto out;
} }
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL, ret = nbd_receive_negotiate(QIO_CHANNEL(sioc),
NULL, NULL, NULL, &info, &local_error); NULL, NULL, NULL, &info, &local_error);
if (ret < 0) { if (ret < 0) {
if (local_error) { if (local_error) {
@ -318,6 +405,7 @@ static void *nbd_client_thread(void *arg)
} }
close(fd); close(fd);
object_unref(OBJECT(sioc)); object_unref(OBJECT(sioc));
g_free(info.name);
kill(getpid(), SIGTERM); kill(getpid(), SIGTERM);
return (void *) EXIT_SUCCESS; return (void *) EXIT_SUCCESS;
@ -326,6 +414,7 @@ out_fd:
out_socket: out_socket:
object_unref(OBJECT(sioc)); object_unref(OBJECT(sioc));
out: out:
g_free(info.name);
kill(getpid(), SIGTERM); kill(getpid(), SIGTERM);
return (void *) EXIT_FAILURE; return (void *) EXIT_FAILURE;
} }
@ -423,7 +512,8 @@ static QemuOptsList qemu_object_opts = {
static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp) static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, bool list,
Error **errp)
{ {
Object *obj; Object *obj;
QCryptoTLSCreds *creds; QCryptoTLSCreds *creds;
@ -443,11 +533,19 @@ static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
return NULL; return NULL;
} }
if (list) {
if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
error_setg(errp,
"Expecting TLS credentials with a client endpoint");
return NULL;
}
} else {
if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
error_setg(errp, error_setg(errp,
"Expecting TLS credentials with a server endpoint"); "Expecting TLS credentials with a server endpoint");
return NULL; return NULL;
} }
}
object_ref(obj); object_ref(obj);
return creds; return creds;
} }
@ -469,7 +567,8 @@ static void setup_address_and_port(const char **address, const char **port)
static const char *socket_activation_validate_opts(const char *device, static const char *socket_activation_validate_opts(const char *device,
const char *sockpath, const char *sockpath,
const char *address, const char *address,
const char *port) const char *port,
bool list)
{ {
if (device != NULL) { if (device != NULL) {
return "NBD device can't be set when using socket activation"; return "NBD device can't be set when using socket activation";
@ -487,6 +586,10 @@ static const char *socket_activation_validate_opts(const char *device,
return "TCP port number can't be set when using socket activation"; return "TCP port number can't be set when using socket activation";
} }
if (list) {
return "List mode is incompatible with socket activation";
}
return NULL; return NULL;
} }
@ -500,17 +603,17 @@ int main(int argc, char **argv)
{ {
BlockBackend *blk; BlockBackend *blk;
BlockDriverState *bs; BlockDriverState *bs;
off_t dev_offset = 0; uint64_t dev_offset = 0;
uint16_t nbdflags = 0; uint16_t nbdflags = 0;
bool disconnect = false; bool disconnect = false;
const char *bindto = NULL; const char *bindto = NULL;
const char *port = NULL; const char *port = NULL;
char *sockpath = NULL; char *sockpath = NULL;
char *device = NULL; char *device = NULL;
off_t fd_size; int64_t fd_size;
QemuOpts *sn_opts = NULL; QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL; const char *sn_id_or_name = NULL;
const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:"; const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:L";
struct option lopt[] = { struct option lopt[] = {
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
@ -523,6 +626,7 @@ int main(int argc, char **argv)
{ "bitmap", required_argument, NULL, 'B' }, { "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' }, { "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' }, { "disconnect", no_argument, NULL, 'd' },
{ "list", no_argument, NULL, 'L' },
{ "snapshot", no_argument, NULL, 's' }, { "snapshot", no_argument, NULL, 's' },
{ "load-snapshot", required_argument, NULL, 'l' }, { "load-snapshot", required_argument, NULL, 'l' },
{ "nocache", no_argument, NULL, 'n' }, { "nocache", no_argument, NULL, 'n' },
@ -546,9 +650,8 @@ int main(int argc, char **argv)
}; };
int ch; int ch;
int opt_ind = 0; int opt_ind = 0;
char *end;
int flags = BDRV_O_RDWR; int flags = BDRV_O_RDWR;
int partition = -1; int partition = 0;
int ret = 0; int ret = 0;
bool seen_cache = false; bool seen_cache = false;
bool seen_discard = false; bool seen_discard = false;
@ -558,7 +661,7 @@ int main(int argc, char **argv)
Error *local_err = NULL; Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF; BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
QDict *options = NULL; QDict *options = NULL;
const char *export_name = ""; /* Default export name */ const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL; const char *export_description = NULL;
const char *bitmap = NULL; const char *bitmap = NULL;
const char *tlscredsid = NULL; const char *tlscredsid = NULL;
@ -566,6 +669,7 @@ int main(int argc, char **argv)
bool writethrough = true; bool writethrough = true;
char *trace_file = NULL; char *trace_file = NULL;
bool fork_process = false; bool fork_process = false;
bool list = false;
int old_stderr = -1; int old_stderr = -1;
unsigned socket_activation; unsigned socket_activation;
@ -660,13 +764,8 @@ int main(int argc, char **argv)
port = optarg; port = optarg;
break; break;
case 'o': case 'o':
dev_offset = strtoll (optarg, &end, 0); if (qemu_strtou64(optarg, NULL, 0, &dev_offset) < 0) {
if (*end) { error_report("Invalid offset '%s'", optarg);
error_report("Invalid offset `%s'", optarg);
exit(EXIT_FAILURE);
}
if (dev_offset < 0) {
error_report("Offset must be positive `%s'", optarg);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
break; break;
@ -688,13 +787,9 @@ int main(int argc, char **argv)
flags &= ~BDRV_O_RDWR; flags &= ~BDRV_O_RDWR;
break; break;
case 'P': case 'P':
partition = strtol(optarg, &end, 0); if (qemu_strtoi(optarg, NULL, 0, &partition) < 0 ||
if (*end) { partition < 1 || partition > 8) {
error_report("Invalid partition `%s'", optarg); error_report("Invalid partition '%s'", optarg);
exit(EXIT_FAILURE);
}
if (partition < 1 || partition > 8) {
error_report("Invalid partition %d", partition);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
break; break;
@ -715,15 +810,11 @@ int main(int argc, char **argv)
device = optarg; device = optarg;
break; break;
case 'e': case 'e':
shared = strtol(optarg, &end, 0); if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
if (*end) { shared < 1) {
error_report("Invalid shared device number '%s'", optarg); error_report("Invalid shared device number '%s'", optarg);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (shared < 1) {
error_report("Shared device number must be greater than 0");
exit(EXIT_FAILURE);
}
break; break;
case 'f': case 'f':
fmt = optarg; fmt = optarg;
@ -772,13 +863,33 @@ int main(int argc, char **argv)
case QEMU_NBD_OPT_FORK: case QEMU_NBD_OPT_FORK:
fork_process = true; fork_process = true;
break; break;
case 'L':
list = true;
break;
} }
} }
if ((argc - optind) != 1) { if (list) {
if (argc != optind) {
error_report("List mode is incompatible with a file name");
exit(EXIT_FAILURE);
}
if (export_name || export_description || dev_offset || partition ||
device || disconnect || fmt || sn_id_or_name || bitmap ||
seen_aio || seen_discard || seen_cache) {
error_report("List mode is incompatible with per-device settings");
exit(EXIT_FAILURE);
}
if (fork_process) {
error_report("List mode is incompatible with forking");
exit(EXIT_FAILURE);
}
} else if ((argc - optind) != 1) {
error_report("Invalid number of arguments"); error_report("Invalid number of arguments");
error_printf("Try `%s --help' for more information.\n", argv[0]); error_printf("Try `%s --help' for more information.\n", argv[0]);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} else if (!export_name) {
export_name = "";
} }
qemu_opts_foreach(&qemu_object_opts, qemu_opts_foreach(&qemu_object_opts,
@ -797,7 +908,8 @@ int main(int argc, char **argv)
} else { } else {
/* Using socket activation - check user didn't use -p etc. */ /* Using socket activation - check user didn't use -p etc. */
const char *err_msg = socket_activation_validate_opts(device, sockpath, const char *err_msg = socket_activation_validate_opts(device, sockpath,
bindto, port); bindto, port,
list);
if (err_msg != NULL) { if (err_msg != NULL) {
error_report("%s", err_msg); error_report("%s", err_msg);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -820,7 +932,7 @@ int main(int argc, char **argv)
error_report("TLS is not supported with a host device"); error_report("TLS is not supported with a host device");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
tlscreds = nbd_get_tls_creds(tlscredsid, &local_err); tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
if (local_err) { if (local_err) {
error_report("Failed to get TLS creds %s", error_report("Failed to get TLS creds %s",
error_get_pretty(local_err)); error_get_pretty(local_err));
@ -828,6 +940,11 @@ int main(int argc, char **argv)
} }
} }
if (list) {
saddr = nbd_build_socket_address(sockpath, bindto, port);
return qemu_nbd_client_list(saddr, tlscreds, bindto);
}
#if !HAVE_NBD_DEVICE #if !HAVE_NBD_DEVICE
if (disconnect || device) { if (disconnect || device) {
error_report("Kernel /dev/nbdN support not available"); error_report("Kernel /dev/nbdN support not available");
@ -1005,20 +1122,37 @@ int main(int argc, char **argv)
} }
if (dev_offset >= fd_size) { if (dev_offset >= fd_size) {
error_report("Offset (%lld) has to be smaller than the image size " error_report("Offset (%" PRIu64 ") has to be smaller than the image "
"(%lld)", "size (%" PRId64 ")", dev_offset, fd_size);
(long long int)dev_offset, (long long int)fd_size);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
fd_size -= dev_offset; fd_size -= dev_offset;
if (partition != -1) { if (partition) {
ret = find_partition(blk, partition, &dev_offset, &fd_size); uint64_t limit;
if (dev_offset) {
error_report("Cannot request partition and offset together");
exit(EXIT_FAILURE);
}
ret = find_partition(blk, partition, &dev_offset, &limit);
if (ret < 0) { if (ret < 0) {
error_report("Could not find partition %d: %s", partition, error_report("Could not find partition %d: %s", partition,
strerror(-ret)); strerror(-ret));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/*
* MBR partition limits are (32-bit << 9); this assert lets
* the compiler know that we can't overflow 64 bits.
*/
assert(dev_offset + limit >= dev_offset);
if (dev_offset + limit > fd_size) {
error_report("Discovered partition %d at offset %" PRIu64
" size %" PRIu64 ", but size exceeds file length %"
PRId64, partition, dev_offset, limit, fd_size);
exit(EXIT_FAILURE);
}
fd_size = limit;
} }
export = nbd_export_new(bs, dev_offset, fd_size, export_name, export = nbd_export_new(bs, dev_offset, fd_size, export_name,

View File

@ -2,6 +2,8 @@
@c man begin SYNOPSIS @c man begin SYNOPSIS
@command{qemu-nbd} [OPTION]... @var{filename} @command{qemu-nbd} [OPTION]... @var{filename}
@command{qemu-nbd} @option{-L} [OPTION]...
@command{qemu-nbd} @option{-d} @var{dev} @command{qemu-nbd} @option{-d} @var{dev}
@c man end @c man end
@end example @end example
@ -10,11 +12,19 @@
Export a QEMU disk image using the NBD protocol. Export a QEMU disk image using the NBD protocol.
Other uses:
@itemize
@item
Bind a /dev/nbdX block device to a QEMU server (on Linux).
@item
As a client to query exports of a remote NBD server.
@end itemize
@c man end @c man end
@c man begin OPTIONS @c man begin OPTIONS
@var{filename} is a disk image filename, or a set of block @var{filename} is a disk image filename, or a set of block
driver options if @var{--image-opts} is specified. driver options if @option{--image-opts} is specified.
@var{dev} is an NBD device. @var{dev} is an NBD device.
@ -25,26 +35,29 @@ See the @code{qemu(1)} manual page for full details of the properties
supported. The common object types that it makes sense to define are the supported. The common object types that it makes sense to define are the
@code{secret} object, which is used to supply passwords and/or encryption @code{secret} object, which is used to supply passwords and/or encryption
keys, and the @code{tls-creds} object, which is used to supply TLS keys, and the @code{tls-creds} object, which is used to supply TLS
credentials for the qemu-nbd server. credentials for the qemu-nbd server or client.
@item -p, --port=@var{port} @item -p, --port=@var{port}
The TCP port to listen on (default @samp{10809}) The TCP port to listen on as a server, or connect to as a client
(default @samp{10809}).
@item -o, --offset=@var{offset} @item -o, --offset=@var{offset}
The offset into the image The offset into the image.
@item -b, --bind=@var{iface} @item -b, --bind=@var{iface}
The interface to bind to (default @samp{0.0.0.0}) The interface to bind to as a server, or connect to as a client
(default @samp{0.0.0.0}).
@item -k, --socket=@var{path} @item -k, --socket=@var{path}
Use a unix socket with path @var{path} Use a unix socket with path @var{path}.
@item --image-opts @item --image-opts
Treat @var{filename} as a set of image options, instead of a plain Treat @var{filename} as a set of image options, instead of a plain
filename. If this flag is specified, the @var{-f} flag should filename. If this flag is specified, the @var{-f} flag should
not be used, instead the '@code{format=}' option should be set. not be used, instead the '@code{format=}' option should be set.
@item -f, --format=@var{fmt} @item -f, --format=@var{fmt}
Force the use of the block driver for format @var{fmt} instead of Force the use of the block driver for format @var{fmt} instead of
auto-detecting auto-detecting.
@item -r, --read-only @item -r, --read-only
Export the disk as read-only Export the disk as read-only.
@item -P, --partition=@var{num} @item -P, --partition=@var{num}
Only expose partition @var{num} Only expose MBR partition @var{num}. Understands physical partitions
1-4 and logical partitions 5-8.
@item -B, --bitmap=@var{name} @item -B, --bitmap=@var{name}
If @var{filename} has a qcow2 persistent bitmap @var{name}, expose If @var{filename} has a qcow2 persistent bitmap @var{name}, expose
that bitmap via the ``qemu:dirty-bitmap:@var{name}'' context that bitmap via the ``qemu:dirty-bitmap:@var{name}'' context
@ -52,7 +65,7 @@ accessible through NBD_OPT_SET_META_CONTEXT.
@item -s, --snapshot @item -s, --snapshot
Use @var{filename} as an external snapshot, create a temporary Use @var{filename} as an external snapshot, create a temporary
file with backing_file=@var{filename}, redirect the write to file with backing_file=@var{filename}, redirect the write to
the temporary one the temporary one.
@item -l, --load-snapshot=@var{snapshot_param} @item -l, --load-snapshot=@var{snapshot_param}
Load an internal snapshot inside @var{filename} and export it Load an internal snapshot inside @var{filename} and export it
as an read-only device, @var{snapshot_param} format is as an read-only device, @var{snapshot_param} format is
@ -76,31 +89,38 @@ driver-specific optimized zero write commands. @var{detect-zeroes} is one of
converts a zero write to an unmap operation and can only be used if converts a zero write to an unmap operation and can only be used if
@var{discard} is set to @samp{unmap}. The default is @samp{off}. @var{discard} is set to @samp{unmap}. The default is @samp{off}.
@item -c, --connect=@var{dev} @item -c, --connect=@var{dev}
Connect @var{filename} to NBD device @var{dev} Connect @var{filename} to NBD device @var{dev} (Linux only).
@item -d, --disconnect @item -d, --disconnect
Disconnect the device @var{dev} Disconnect the device @var{dev} (Linux only).
@item -e, --shared=@var{num} @item -e, --shared=@var{num}
Allow up to @var{num} clients to share the device (default @samp{1}) Allow up to @var{num} clients to share the device (default
@samp{1}). Safe for readers, but for now, consistency is not
guaranteed between multiple writers.
@item -t, --persistent @item -t, --persistent
Don't exit on the last connection Don't exit on the last connection.
@item -x, --export-name=@var{name} @item -x, --export-name=@var{name}
Set the NBD volume export name. This switches the server to use Set the NBD volume export name (default of a zero-length string).
the new style NBD protocol negotiation
@item -D, --description=@var{description} @item -D, --description=@var{description}
Set the NBD volume export description, as a human-readable Set the NBD volume export description, as a human-readable
string. Requires the use of @option{-x} string.
@item -L, --list
Connect as a client and list all details about the exports exposed by
a remote NBD server. This enables list mode, and is incompatible
with options that change behavior related to a specific export (such as
@option{--export-name}, @option{--offset}, ...).
@item --tls-creds=ID @item --tls-creds=ID
Enable mandatory TLS encryption for the server by setting the ID Enable mandatory TLS encryption for the server by setting the ID
of the TLS credentials object previously created with the --object of the TLS credentials object previously created with the --object
option. option; or provide the credentials needed for connecting as a client
in list mode.
@item --fork @item --fork
Fork off the server process and exit the parent once the server is running. Fork off the server process and exit the parent once the server is running.
@item -v, --verbose @item -v, --verbose
Display extra debugging information Display extra debugging information.
@item -h, --help @item -h, --help
Display this help and exit Display this help and exit.
@item -V, --version @item -V, --version
Display version information and exit Display version information and exit.
@item -T, --trace [[enable=]@var{pattern}][,events=@var{file}][,file=@var{file}] @item -T, --trace [[enable=]@var{pattern}][,events=@var{file}][,file=@var{file}]
@findex --trace @findex --trace
@include qemu-option-trace.texi @include qemu-option-trace.texi
@ -108,6 +128,63 @@ Display version information and exit
@c man end @c man end
@c man begin EXAMPLES
Start a server listening on port 10809 that exposes only the
guest-visible contents of a qcow2 file, with no TLS encryption, and
with the default export name (an empty string). The command is
one-shot, and will block until the first successful client
disconnects:
@example
qemu-nbd -f qcow2 file.qcow2
@end example
Start a long-running server listening with encryption on port 10810,
and require clients to have a correct X.509 certificate to connect to
a 1 megabyte subset of a raw file, using the export name 'subset':
@example
qemu-nbd \
--object tls-creds-x509,id=tls0,endpoint=server,dir=/path/to/qemutls \
--tls-creds tls0 -t -x subset -p 10810 \
--image-opts driver=raw,offset=1M,size=1M,file.driver=file,file.filename=file.raw
@end example
Serve a read-only copy of just the first MBR partition of a guest
image over a Unix socket with as many as 5 simultaneous readers, with
a persistent process forked as a daemon:
@example
qemu-nbd --fork --persistent --shared=5 --socket=/path/to/sock \
--partition=1 --read-only --format=qcow2 file.qcow2
@end example
Expose the guest-visible contents of a qcow2 file via a block device
/dev/nbd0 (and possibly creating /dev/nbd0p1 and friends for
partitions found within), then disconnect the device when done.
Access to bind qemu-nbd to an /dev/nbd device generally requires root
privileges, and may also require the execution of @code{modprobe nbd}
to enable the kernel NBD client module. @emph{CAUTION}: Do not use
this method to mount filesystems from an untrusted guest image - a
malicious guest may have prepared the image to attempt to trigger
kernel bugs in partition probing or file system mounting.
@example
qemu-nbd -c /dev/nbd0 -f qcow2 file.qcow2
qemu-nbd -d /dev/nbd0
@end example
Query a remote server to see details about what export(s) it is
serving on port 10809, and authenticating via PSK:
@example
qemu-nbd \
--object tls-creds-psk,id=tls0,dir=/tmp/keys,username=eblake,endpoint=client \
--tls-creds tls0 -L -b remote.example.com
@end example
@c man end
@ignore @ignore
@setfilename qemu-nbd @setfilename qemu-nbd

View File

@ -398,7 +398,7 @@ $sects{NAME} = "$fn \- $tl\n";
$sects{FOOTNOTES} .= "=back\n" if exists $sects{FOOTNOTES}; $sects{FOOTNOTES} .= "=back\n" if exists $sects{FOOTNOTES};
for $sect (qw(NAME SYNOPSIS DESCRIPTION OPTIONS ENVIRONMENT FILES for $sect (qw(NAME SYNOPSIS DESCRIPTION OPTIONS ENVIRONMENT FILES
BUGS NOTES FOOTNOTES SEEALSO AUTHOR COPYRIGHT)) { BUGS NOTES FOOTNOTES EXAMPLES SEEALSO AUTHOR COPYRIGHT)) {
if(exists $sects{$sect}) { if(exists $sects{$sect}) {
$head = $sect; $head = $sect;
$head =~ s/SEEALSO/SEE ALSO/; $head =~ s/SEEALSO/SEE ALSO/;

View File

@ -127,6 +127,7 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-start",
"arguments":{"addr":{"type":"unix", "arguments":{"addr":{"type":"unix",
"data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server "data":{"path":"'"$TEST_DIR/nbd"1'"}}}}' "error" # Attempt second server
$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "bitmap":"b"}}' "return" "arguments":{"device":"n", "bitmap":"b"}}' "return"
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
@ -142,6 +143,7 @@ _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
_send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add", _send_qemu_cmd $QEMU_HANDLE '{"execute":"nbd-server-add",
"arguments":{"device":"n", "name":"n2", "writable":true, "arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return" "bitmap":"b2"}}' "return"
$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
echo echo
echo "=== Contrast normal status to large granularity dirty-bitmap ===" echo "=== Contrast normal status to large granularity dirty-bitmap ==="

View File

@ -30,12 +30,32 @@ wrote 2097152/2097152 bytes at offset 2097152
{"error": {"class": "GenericError", "desc": "NBD server not running"}} {"error": {"class": "GenericError", "desc": "NBD server not running"}}
{"return": {}} {"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server already running"}} {"error": {"class": "GenericError", "desc": "NBD server already running"}}
exports available: 0
{"return": {}} {"return": {}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}} {"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}} {"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}}
{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}} {"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}} {"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}}
{"return": {}} {"return": {}}
exports available: 2
export: 'n'
size: 4194304
flags: 0x4ef ( readonly flush fua trim zeroes df cache )
min block: 512
opt block: 4096
max block: 33554432
available meta contexts: 2
base:allocation
qemu:dirty-bitmap:b
export: 'n2'
size: 4194304
flags: 0x4ed ( flush fua trim zeroes df cache )
min block: 512
opt block: 4096
max block: 33554432
available meta contexts: 2
base:allocation
qemu:dirty-bitmap:b2
=== Contrast normal status to large granularity dirty-bitmap === === Contrast normal status to large granularity dirty-bitmap ===

View File

@ -2,7 +2,7 @@
# #
# Test NBD TLS certificate / authorization integration # Test NBD TLS certificate / authorization integration
# #
# Copyright (C) 2018 Red Hat, Inc. # Copyright (C) 2018-2019 Red Hat, Inc.
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -30,6 +30,7 @@ _cleanup()
{ {
nbd_server_stop nbd_server_stop
_cleanup_test_img _cleanup_test_img
rm -f "$TEST_DIR/server.log"
tls_x509_cleanup tls_x509_cleanup
} }
trap "_cleanup; exit \$status" 0 1 2 3 15 trap "_cleanup; exit \$status" 0 1 2 3 15
@ -66,12 +67,14 @@ $QEMU_IO -c 'w -P 0x11 1m 1m' "$TEST_IMG" | _filter_qemu_io
echo echo
echo "== check TLS client to plain server fails ==" echo "== check TLS client to plain server fails =="
nbd_server_start_tcp_socket -f $IMGFMT "$TEST_IMG" nbd_server_start_tcp_socket -f $IMGFMT "$TEST_IMG" 2> "$TEST_DIR/server.log"
$QEMU_IMG info --image-opts \ obj=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
--object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \ $QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \ driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g" 2>&1 | sed "s/$nbd_tcp_port/PORT/g"
$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
--tls-creds=tls0
nbd_server_stop nbd_server_stop
@ -81,23 +84,28 @@ echo "== check plain client to TLS server fails =="
nbd_server_start_tcp_socket \ nbd_server_start_tcp_socket \
--object tls-creds-x509,dir=${tls_dir}/server1,endpoint=server,id=tls0,verify-peer=yes \ --object tls-creds-x509,dir=${tls_dir}/server1,endpoint=server,id=tls0,verify-peer=yes \
--tls-creds tls0 \ --tls-creds tls0 \
-f $IMGFMT "$TEST_IMG" -f $IMGFMT "$TEST_IMG" 2>> "$TEST_DIR/server.log"
$QEMU_IMG info nbd://localhost:$nbd_tcp_port 2>&1 | sed "s/$nbd_tcp_port/PORT/g" $QEMU_IMG info nbd://localhost:$nbd_tcp_port 2>&1 | sed "s/$nbd_tcp_port/PORT/g"
$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port
echo echo
echo "== check TLS works ==" echo "== check TLS works =="
$QEMU_IMG info --image-opts \ obj=tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0
--object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \ $QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \ driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g" 2>&1 | sed "s/$nbd_tcp_port/PORT/g"
$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
--tls-creds=tls0
echo echo
echo "== check TLS with different CA fails ==" echo "== check TLS with different CA fails =="
$QEMU_IMG info --image-opts \ obj=tls-creds-x509,dir=${tls_dir}/client2,endpoint=client,id=tls0
--object tls-creds-x509,dir=${tls_dir}/client2,endpoint=client,id=tls0 \ $QEMU_IMG info --image-opts --object $obj \
driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \ driver=nbd,host=$nbd_tcp_addr,port=$nbd_tcp_port,tls-creds=tls0 \
2>&1 | sed "s/$nbd_tcp_port/PORT/g" 2>&1 | sed "s/$nbd_tcp_port/PORT/g"
$QEMU_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port --object $obj \
--tls-creds=tls0
echo echo
echo "== perform I/O over TLS ==" echo "== perform I/O over TLS =="
@ -109,6 +117,10 @@ $QEMU_IO -c 'r -P 0x11 1m 1m' -c 'w -P 0x22 1m 1m' --image-opts \
$QEMU_IO -f $IMGFMT -r -U -c 'r -P 0x22 1m 1m' "$TEST_IMG" | _filter_qemu_io $QEMU_IO -f $IMGFMT -r -U -c 'r -P 0x22 1m 1m' "$TEST_IMG" | _filter_qemu_io
echo
echo "== final server log =="
cat "$TEST_DIR/server.log"
# success, all done # success, all done
echo "*** done" echo "*** done"
rm -f $seq.full rm -f $seq.full

View File

@ -15,20 +15,33 @@ wrote 1048576/1048576 bytes at offset 1048576
== check TLS client to plain server fails == == check TLS client to plain server fails ==
qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': Denied by server for option 5 (starttls) qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': Denied by server for option 5 (starttls)
server reported: TLS not configured server reported: TLS not configured
qemu-nbd: Denied by server for option 5 (starttls)
server reported: TLS not configured
== check plain client to TLS server fails == == check plain client to TLS server fails ==
qemu-img: Could not open 'nbd://localhost:PORT': TLS negotiation required before option 8 (structured reply) qemu-img: Could not open 'nbd://localhost:PORT': TLS negotiation required before option 8 (structured reply)
server reported: Option 0x8 not permitted before TLS server reported: Option 0x8 not permitted before TLS
qemu-nbd: TLS negotiation required before option 8 (structured reply)
server reported: Option 0x8 not permitted before TLS
== check TLS works == == check TLS works ==
image: nbd://127.0.0.1:PORT image: nbd://127.0.0.1:PORT
file format: nbd file format: nbd
virtual size: 64M (67108864 bytes) virtual size: 64M (67108864 bytes)
disk size: unavailable disk size: unavailable
exports available: 1
export: ''
size: 67108864
flags: 0x4ed ( flush fua trim zeroes df cache )
min block: 512
opt block: 4096
max block: 33554432
available meta contexts: 1
base:allocation
== check TLS with different CA fails == == check TLS with different CA fails ==
qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': The certificate hasn't got a known issuer qemu-img: Could not open 'driver=nbd,host=127.0.0.1,port=PORT,tls-creds=tls0': The certificate hasn't got a known issuer
qemu-nbd: The certificate hasn't got a known issuer
== perform I/O over TLS == == perform I/O over TLS ==
read 1048576/1048576 bytes at offset 1048576 read 1048576/1048576 bytes at offset 1048576
@ -37,4 +50,8 @@ wrote 1048576/1048576 bytes at offset 1048576
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 1048576/1048576 bytes at offset 1048576 read 1048576/1048576 bytes at offset 1048576
1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== final server log ==
qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
qemu-nbd: option negotiation failed: Verify failed: No certificate was found.
*** done *** done