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.texi docs/interop/qemu-qmp-qapi.texi
$(filter %.1 %.7 %.8,$(DOCS)): scripts/texi2pod.pl
# Reports/Analysis
%/coverage-report.html:

View File

@ -249,11 +249,11 @@ static int nbd_parse_blockstatus_payload(NBDClientSession *client,
}
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 "
"NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context "
"id is %d", context_id,
client->info.meta_base_allocation_id);
client->info.context_id);
return -EINVAL;
}
@ -999,10 +999,11 @@ int nbd_client_init(BlockDriverState *bs,
client->info.structured_reply = true;
client->info.base_allocation = true;
client->info.x_dirty_bitmap = g_strdup(x_dirty_bitmap);
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
tlscreds, hostname,
client->info.name = g_strdup(export ?: "");
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), tlscreds, hostname,
&client->ioc, &client->info, errp);
g_free(client->info.x_dirty_bitmap);
g_free(client->info.name);
if (ret < 0) {
logout("Failed to negotiate with the NBD server\n");
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;
BlockBackend *on_eject_blk;
NBDExport *exp;
int64_t len;
if (!nbd_server) {
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;
}
len = bdrv_getlength(bs);
if (len < 0) {
error_setg_errno(errp, -len,
"Failed to determine the NBD export's length");
return;
}
if (!has_writable) {
writable = false;
}
@ -175,7 +183,7 @@ void qmp_nbd_server_add(const char *device, bool has_name, const char *name,
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,
NULL, false, on_eject_blk, errp);
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>
*
* Network Block Device
@ -263,26 +263,39 @@ struct NBDExportInfo {
bool request_sizes;
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
* updated by server results during nbd_receive_negotiate() */
bool structured_reply;
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;
uint16_t flags;
uint32_t min_block;
uint32_t opt_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;
int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
QCryptoTLSCreds *tlscreds, const char *hostname,
QIOChannel **outioc, NBDExportInfo *info,
Error **errp);
int nbd_receive_negotiate(QIOChannel *ioc, QCryptoTLSCreds *tlscreds,
const char *hostname, QIOChannel **outioc,
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);
int nbd_init(int fd, QIOChannelSocket *sioc, NBDExportInfo *info,
Error **errp);
int nbd_send_request(QIOChannel *ioc, NBDRequest *request);
@ -294,8 +307,8 @@ int nbd_errno_to_system_errno(int err);
typedef struct NBDExport NBDExport;
typedef struct NBDClient NBDClient;
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
const char *name, const char *description,
NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp);

File diff suppressed because it is too large Load Diff

View File

@ -77,8 +77,8 @@ struct NBDExport {
BlockBackend *blk;
char *name;
char *description;
off_t dev_offset;
off_t size;
uint64_t dev_offset;
uint64_t size;
uint16_t nbdflags;
QTAILQ_HEAD(, NBDClient) clients;
QTAILQ_ENTRY(NBDExport) next;
@ -1455,8 +1455,8 @@ static void nbd_eject_notifier(Notifier *n, void *data)
nbd_export_close(exp);
}
NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size,
const char *name, const char *description,
NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
uint64_t size, const char *name, const char *desc,
const char *bitmap, uint16_t nbdflags,
void (*close)(NBDExport *), bool writethrough,
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;
QTAILQ_INIT(&exp->clients);
exp->blk = blk;
assert(dev_offset <= INT64_MAX);
exp->dev_offset = dev_offset;
exp->name = g_strdup(name);
exp->description = g_strdup(description);
exp->description = g_strdup(desc);
exp->nbdflags = nbdflags;
exp->size = size < 0 ? blk_getlength(blk) : size;
if (exp->size < 0) {
error_setg_errno(errp, -exp->size,
"Failed to determine the NBD export's length");
goto fail;
}
exp->size -= exp->size % BDRV_SECTOR_SIZE;
assert(size <= INT64_MAX - dev_offset);
exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
if (bitmap) {
BdrvDirtyBitmap *bm = NULL;
@ -2134,10 +2130,10 @@ static int nbd_co_receive_request(NBDRequestData *req, NBDRequest *request,
return -EROFS;
}
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
", Size: %" PRIu64, request->from, request->len,
(uint64_t)client->exp->size);
client->exp->size);
return (request->type == NBD_CMD_WRITE ||
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_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_opt_go_start(const char *name) "Attempting NBD_OPT_GO for export '%s'"
nbd_opt_go_success(void) "Export is good to go"
nbd_opt_go_info_unknown(int info, const char *name) "Ignoring unknown info %d (%s)"
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_receive_list(const char *name, const char *desc) "export list includes '%s', description '%s'"
nbd_opt_info_go_start(const char *opt, const char *name) "Attempting %s for export '%s'"
nbd_opt_info_go_success(const char *opt) "Export is ready after %s request"
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_success(const char *wantname) "Found desired export name '%s'"
nbd_receive_starttls_new_client(void) "Setting up TLS"
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_reply(const char *context, uint32_t id) "Received mapping of context %s to id %" PRIu32
nbd_receive_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%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 *optname, const char *context, uint32_t id) "Received %s mapping of %s to id %" PRIu32
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_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_init_set_socket(void) "Setting NBD socket"
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) (
"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"
" -h, --help display this help 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"
"\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"
" passwords and/or encryption keys\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"
"\n"
QEMU_HELP_BOTTOM "\n"
, name, NBD_DEFAULT_PORT, "DEVICE");
, name, name, NBD_DEFAULT_PORT, "DEVICE");
}
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,
off_t *offset, off_t *size)
uint64_t *offset, uint64_t *size)
{
struct partition_record mbr[4];
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
static void *show_parts(void *arg)
{
@ -264,7 +351,7 @@ static void *show_parts(void *arg)
static void *nbd_client_thread(void *arg)
{
char *device = arg;
NBDExportInfo info = { .request_sizes = false, };
NBDExportInfo info = { .request_sizes = false, .name = g_strdup("") };
QIOChannelSocket *sioc;
int fd;
int ret;
@ -279,7 +366,7 @@ static void *nbd_client_thread(void *arg)
goto out;
}
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL,
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc),
NULL, NULL, NULL, &info, &local_error);
if (ret < 0) {
if (local_error) {
@ -318,6 +405,7 @@ static void *nbd_client_thread(void *arg)
}
close(fd);
object_unref(OBJECT(sioc));
g_free(info.name);
kill(getpid(), SIGTERM);
return (void *) EXIT_SUCCESS;
@ -326,6 +414,7 @@ out_fd:
out_socket:
object_unref(OBJECT(sioc));
out:
g_free(info.name);
kill(getpid(), SIGTERM);
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;
QCryptoTLSCreds *creds;
@ -443,10 +533,18 @@ static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
return NULL;
}
if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
error_setg(errp,
"Expecting TLS credentials with a server endpoint");
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) {
error_setg(errp,
"Expecting TLS credentials with a server endpoint");
return NULL;
}
}
object_ref(obj);
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,
const char *sockpath,
const char *address,
const char *port)
const char *port,
bool list)
{
if (device != NULL) {
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";
}
if (list) {
return "List mode is incompatible with socket activation";
}
return NULL;
}
@ -500,17 +603,17 @@ int main(int argc, char **argv)
{
BlockBackend *blk;
BlockDriverState *bs;
off_t dev_offset = 0;
uint64_t dev_offset = 0;
uint16_t nbdflags = 0;
bool disconnect = false;
const char *bindto = NULL;
const char *port = NULL;
char *sockpath = NULL;
char *device = NULL;
off_t fd_size;
int64_t fd_size;
QemuOpts *sn_opts = 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[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@ -523,6 +626,7 @@ int main(int argc, char **argv)
{ "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' },
{ "list", no_argument, NULL, 'L' },
{ "snapshot", no_argument, NULL, 's' },
{ "load-snapshot", required_argument, NULL, 'l' },
{ "nocache", no_argument, NULL, 'n' },
@ -546,9 +650,8 @@ int main(int argc, char **argv)
};
int ch;
int opt_ind = 0;
char *end;
int flags = BDRV_O_RDWR;
int partition = -1;
int partition = 0;
int ret = 0;
bool seen_cache = false;
bool seen_discard = false;
@ -558,7 +661,7 @@ int main(int argc, char **argv)
Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
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 *bitmap = NULL;
const char *tlscredsid = NULL;
@ -566,6 +669,7 @@ int main(int argc, char **argv)
bool writethrough = true;
char *trace_file = NULL;
bool fork_process = false;
bool list = false;
int old_stderr = -1;
unsigned socket_activation;
@ -660,13 +764,8 @@ int main(int argc, char **argv)
port = optarg;
break;
case 'o':
dev_offset = strtoll (optarg, &end, 0);
if (*end) {
error_report("Invalid offset `%s'", optarg);
exit(EXIT_FAILURE);
}
if (dev_offset < 0) {
error_report("Offset must be positive `%s'", optarg);
if (qemu_strtou64(optarg, NULL, 0, &dev_offset) < 0) {
error_report("Invalid offset '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
@ -688,13 +787,9 @@ int main(int argc, char **argv)
flags &= ~BDRV_O_RDWR;
break;
case 'P':
partition = strtol(optarg, &end, 0);
if (*end) {
error_report("Invalid partition `%s'", optarg);
exit(EXIT_FAILURE);
}
if (partition < 1 || partition > 8) {
error_report("Invalid partition %d", partition);
if (qemu_strtoi(optarg, NULL, 0, &partition) < 0 ||
partition < 1 || partition > 8) {
error_report("Invalid partition '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
@ -715,15 +810,11 @@ int main(int argc, char **argv)
device = optarg;
break;
case 'e':
shared = strtol(optarg, &end, 0);
if (*end) {
if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
shared < 1) {
error_report("Invalid shared device number '%s'", optarg);
exit(EXIT_FAILURE);
}
if (shared < 1) {
error_report("Shared device number must be greater than 0");
exit(EXIT_FAILURE);
}
break;
case 'f':
fmt = optarg;
@ -772,13 +863,33 @@ int main(int argc, char **argv)
case QEMU_NBD_OPT_FORK:
fork_process = true;
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_printf("Try `%s --help' for more information.\n", argv[0]);
exit(EXIT_FAILURE);
} else if (!export_name) {
export_name = "";
}
qemu_opts_foreach(&qemu_object_opts,
@ -797,7 +908,8 @@ int main(int argc, char **argv)
} else {
/* Using socket activation - check user didn't use -p etc. */
const char *err_msg = socket_activation_validate_opts(device, sockpath,
bindto, port);
bindto, port,
list);
if (err_msg != NULL) {
error_report("%s", err_msg);
exit(EXIT_FAILURE);
@ -820,7 +932,7 @@ int main(int argc, char **argv)
error_report("TLS is not supported with a host device");
exit(EXIT_FAILURE);
}
tlscreds = nbd_get_tls_creds(tlscredsid, &local_err);
tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
if (local_err) {
error_report("Failed to get TLS creds %s",
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 (disconnect || device) {
error_report("Kernel /dev/nbdN support not available");
@ -1005,20 +1122,37 @@ int main(int argc, char **argv)
}
if (dev_offset >= fd_size) {
error_report("Offset (%lld) has to be smaller than the image size "
"(%lld)",
(long long int)dev_offset, (long long int)fd_size);
error_report("Offset (%" PRIu64 ") has to be smaller than the image "
"size (%" PRId64 ")", dev_offset, fd_size);
exit(EXIT_FAILURE);
}
fd_size -= dev_offset;
if (partition != -1) {
ret = find_partition(blk, partition, &dev_offset, &fd_size);
if (partition) {
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) {
error_report("Could not find partition %d: %s", partition,
strerror(-ret));
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,

View File

@ -2,6 +2,8 @@
@c man begin SYNOPSIS
@command{qemu-nbd} [OPTION]... @var{filename}
@command{qemu-nbd} @option{-L} [OPTION]...
@command{qemu-nbd} @option{-d} @var{dev}
@c man end
@end example
@ -10,11 +12,19 @@
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 begin OPTIONS
@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.
@ -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
@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
credentials for the qemu-nbd server.
credentials for the qemu-nbd server or client.
@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}
The offset into the image
The offset into the image.
@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}
Use a unix socket with path @var{path}
Use a unix socket with path @var{path}.
@item --image-opts
Treat @var{filename} as a set of image options, instead of a plain
filename. If this flag is specified, the @var{-f} flag should
not be used, instead the '@code{format=}' option should be set.
@item -f, --format=@var{fmt}
Force the use of the block driver for format @var{fmt} instead of
auto-detecting
auto-detecting.
@item -r, --read-only
Export the disk as read-only
Export the disk as read-only.
@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}
If @var{filename} has a qcow2 persistent bitmap @var{name}, expose
that bitmap via the ``qemu:dirty-bitmap:@var{name}'' context
@ -52,7 +65,7 @@ accessible through NBD_OPT_SET_META_CONTEXT.
@item -s, --snapshot
Use @var{filename} as an external snapshot, create a temporary
file with backing_file=@var{filename}, redirect the write to
the temporary one
the temporary one.
@item -l, --load-snapshot=@var{snapshot_param}
Load an internal snapshot inside @var{filename} and export it
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
@var{discard} is set to @samp{unmap}. The default is @samp{off}.
@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
Disconnect the device @var{dev}
Disconnect the device @var{dev} (Linux only).
@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
Don't exit on the last connection
Don't exit on the last connection.
@item -x, --export-name=@var{name}
Set the NBD volume export name. This switches the server to use
the new style NBD protocol negotiation
Set the NBD volume export name (default of a zero-length string).
@item -D, --description=@var{description}
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
Enable mandatory TLS encryption for the server by setting the ID
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
Fork off the server process and exit the parent once the server is running.
@item -v, --verbose
Display extra debugging information
Display extra debugging information.
@item -h, --help
Display this help and exit
Display this help and exit.
@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}]
@findex --trace
@include qemu-option-trace.texi
@ -108,6 +128,63 @@ Display version information and exit
@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
@setfilename qemu-nbd

View File

@ -398,7 +398,7 @@ $sects{NAME} = "$fn \- $tl\n";
$sects{FOOTNOTES} .= "=back\n" if exists $sects{FOOTNOTES};
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}) {
$head = $sect;
$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",
"arguments":{"addr":{"type":"unix",
"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",
"arguments":{"device":"n", "bitmap":"b"}}' "return"
_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",
"arguments":{"device":"n", "name":"n2", "writable":true,
"bitmap":"b2"}}' "return"
$QEMU_NBD_PROG -L -k "$TEST_DIR/nbd"
echo
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"}}
{"return": {}}
{"error": {"class": "GenericError", "desc": "NBD server already running"}}
exports available: 0
{"return": {}}
{"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": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"error": {"class": "GenericError", "desc": "Bitmap 'b3' is not found"}}
{"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 ===

View File

@ -2,7 +2,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by
@ -30,6 +30,7 @@ _cleanup()
{
nbd_server_stop
_cleanup_test_img
rm -f "$TEST_DIR/server.log"
tls_x509_cleanup
}
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 "== 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 \
--object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \
obj=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 \
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
@ -81,23 +84,28 @@ echo "== check plain client to TLS server fails =="
nbd_server_start_tcp_socket \
--object tls-creds-x509,dir=${tls_dir}/server1,endpoint=server,id=tls0,verify-peer=yes \
--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_NBD_PROG -L -b $nbd_tcp_addr -p $nbd_tcp_port
echo
echo "== check TLS works =="
$QEMU_IMG info --image-opts \
--object tls-creds-x509,dir=${tls_dir}/client1,endpoint=client,id=tls0 \
obj=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 \
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 "== check TLS with different CA fails =="
$QEMU_IMG info --image-opts \
--object tls-creds-x509,dir=${tls_dir}/client2,endpoint=client,id=tls0 \
obj=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 \
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 "== 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
echo
echo "== final server log =="
cat "$TEST_DIR/server.log"
# success, all done
echo "*** done"
rm -f $seq.full

View File

@ -15,20 +15,33 @@ wrote 1048576/1048576 bytes at offset 1048576
== 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)
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 ==
qemu-img: Could not open 'nbd://localhost:PORT': TLS negotiation required before option 8 (structured reply)
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 ==
image: nbd://127.0.0.1:PORT
file format: nbd
virtual size: 64M (67108864 bytes)
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 ==
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-nbd: The certificate hasn't got a known issuer
== perform I/O over TLS ==
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)
read 1048576/1048576 bytes at offset 1048576
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