The NBD spec allows servers to refuse NBD_OPT_GO to clients that do
not promise to obey block size constraints (see
NBD_REP_ERR_BLOCK_SIZE_REQD). Upcoming patches will make libnbd able
to remember and honor block sizes, but even when we don't honor them,
generally the worst that will happen is the server will send us EINVAL
errors on unaligned I/O, which is better than complete failure to
connect by not even asking about block sizes. All that is required to
ask is to include NBD_INFO_BLOCK_SIZE as a client info in our
NBD_OPT_GO. The NBD spec is clear that servers that don't understand
client requests should silently ignore the request, so it doesn't hurt
to always ask (but if we ever _do_ find a non-compliant server where
it matters, or if we wanted to improve unit testing of a server's
ability to send the REP_ERR_BLOCK_SIZE_REQD error, we could add a knob
to tell libnbd to skip the ask for such servers). For now, we just
stash what we learn (if anything) in internal state; the next patch
will then add API to expose it to the end user.
---
lib/internal.h | 7 +++++++
lib/nbd-protocol.h | 10 +++++++++-
generator/state_machine.ml | 7 +++++++
generator/states-newstyle-opt-go.c | 30 ++++++++++++++++++++++++++----
4 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index 2f1727f..1646b01 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -109,6 +109,11 @@ struct nbd_handle {
uint64_t exportsize;
uint16_t eflags;
+ /* Server block size constraints, or 0 if not advertised. */
+ uint32_t block_minimum;
+ uint32_t block_preferred;
+ uint32_t block_maximum;
+
/* Flags set by the state machine to tell what protocol and whether
* TLS was negotiated.
*/
@@ -168,6 +173,7 @@ struct nbd_handle {
char str[NBD_MAX_STRING];
} __attribute__((packed)) server;
struct nbd_fixed_new_option_reply_info_export export;
+ struct nbd_fixed_new_option_reply_info_block_size block_size;
struct {
struct nbd_fixed_new_option_reply_meta_context context;
char str[NBD_MAX_STRING];
@@ -193,6 +199,7 @@ struct nbd_handle {
uint32_t cflags;
uint32_t len;
uint16_t nrinfos;
+ uint16_t info;
uint32_t nrqueries;
} sbuf;
diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index 90fdbd2..627bc6e 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -154,6 +154,14 @@ struct nbd_fixed_new_option_reply_info_export {
uint16_t eflags; /* per-export flags */
} NBD_ATTRIBUTE_PACKED;
+/* NBD_INFO_BLOCK_SIZE reply (follows fixed_new_option_reply). */
+struct nbd_fixed_new_option_reply_info_block_size {
+ uint16_t info; /* NBD_INFO_BLOCK_SIZE */
+ uint32_t minimum; /* minimum block size */
+ uint32_t preferred; /* preferred block size */
+ uint32_t maximum; /* maximum block size */
+} NBD_ATTRIBUTE_PACKED;
+
/* NBD_REP_SERVER reply (follows fixed_new_option_reply). */
struct nbd_fixed_new_option_reply_server {
uint32_t export_name_len; /* length of export name */
diff --git a/generator/state_machine.ml b/generator/state_machine.ml
index 27b4fa3..144a5f8 100644
--- a/generator/state_machine.ml
+++ b/generator/state_machine.ml
@@ -542,6 +542,13 @@ and newstyle_opt_go_state_machine = [
external_events = [ NotifyWrite, "" ];
};
+ State {
+ default_state with
+ name = "SEND_INFO";
+ comment = "Send newstyle NBD_OPT_GO request for NBD_INFO_BLOCK_SIZE";
+ external_events = [ NotifyWrite, "" ];
+ };
+
State {
default_state with
name = "RECV_REPLY";
diff --git a/generator/states-newstyle-opt-go.c b/generator/states-newstyle-opt-go.c
index 57ad55a..39406fa 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -1,5 +1,5 @@
/* nbd client library in userspace: state machine
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 Red Hat Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -23,7 +23,8 @@ STATE_MACHINE {
h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
h->sbuf.option.option = htobe32 (NBD_OPT_GO);
h->sbuf.option.optlen =
- htobe32 (/* exportnamelen */ 4 + strlen (h->export_name) + /* nrinfos */ 2);
+ htobe32 (/* exportnamelen */ 4 + strlen (h->export_name)
+ + /* nrinfos */ 2 + /* INFO_BLOCK_SIZE */ 2);
h->wbuf = &h->sbuf;
h->wlen = sizeof h->sbuf.option;
h->wflags = MSG_MORE;
@@ -59,7 +60,7 @@ STATE_MACHINE {
switch (send_from_wbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
case 0:
- h->sbuf.nrinfos = 0;
+ h->sbuf.nrinfos = htobe16 (1);
h->wbuf = &h->sbuf;
h->wlen = 2;
SET_NEXT_STATE (%SEND_NRINFOS);
@@ -67,6 +68,17 @@ STATE_MACHINE {
return 0;
NEWSTYLE.OPT_GO.SEND_NRINFOS:
+ switch (send_from_wbuf (h)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return 0;
+ case 0:
+ h->sbuf.info = htobe16 (NBD_INFO_BLOCK_SIZE);
+ h->wbuf = &h->sbuf;
+ h->wlen = 2;
+ SET_NEXT_STATE (%SEND_INFO);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_GO.SEND_INFO:
switch (send_from_wbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
case 0:
@@ -130,8 +142,18 @@ STATE_MACHINE {
return 0;
}
break;
+ case NBD_INFO_BLOCK_SIZE:
+ if (len != sizeof h->sbuf.or.payload.block_size) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: incorrect NBD_INFO_BLOCK_SIZE option reply
length");
+ return 0;
+ }
+ h->block_minimum = be32toh (h->sbuf.or.payload.block_size.minimum);
+ h->block_preferred = be32toh (h->sbuf.or.payload.block_size.preferred);
+ h->block_maximum = be32toh (h->sbuf.or.payload.block_size.maximum);
+ break;
default:
- /* XXX Handle other info types, like NBD_INFO_BLOCK_SIZE */
+ /* XXX Handle other info types, like NBD_INFO_DESCRIPTION */
debug (h, "skipping unknown NBD_REP_INFO type %d",
be16toh (h->sbuf.or.payload.export.info));
break;
--
2.27.0