Skip to content

Commit

Permalink
nbd: implement TLS support in the protocol negotiation
Browse files Browse the repository at this point in the history
This extends the NBD protocol handling code so that it is capable
of negotiating TLS support during the connection setup. This involves
requesting the STARTTLS protocol option before any other NBD options.

Signed-off-by: Daniel P. Berrange <[email protected]>
Message-Id: <[email protected]>
Signed-off-by: Paolo Bonzini <[email protected]>
  • Loading branch information
berrange authored and bonzini committed Feb 16, 2016
1 parent 69b4950 commit f95910f
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 13 deletions.
12 changes: 9 additions & 3 deletions block/nbd-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,10 @@ int nbd_client_init(BlockDriverState *bs, QIOChannelSocket *sioc,
qio_channel_set_blocking(QIO_CHANNEL(sioc), true, NULL);

ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
&client->nbdflags, &client->size, errp);
&client->nbdflags,
NULL, NULL,
&client->ioc,
&client->size, errp);
if (ret < 0) {
logout("Failed to negotiate with the NBD server\n");
return ret;
Expand All @@ -415,8 +418,11 @@ int nbd_client_init(BlockDriverState *bs, QIOChannelSocket *sioc,
qemu_co_mutex_init(&client->free_sema);
client->sioc = sioc;
object_ref(OBJECT(client->sioc));
client->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(client->ioc));

if (!client->ioc) {
client->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(client->ioc));
}

/* Now that we're connected, set the socket to be non-blocking and
* kick the reply mechanism. */
Expand Down
2 changes: 1 addition & 1 deletion blockdev-nbd.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static gboolean nbd_accept(QIOChannel *ioc, GIOCondition condition,
return TRUE;
}

nbd_client_new(NULL, cioc, nbd_client_put);
nbd_client_new(NULL, cioc, NULL, NULL, nbd_client_put);
object_unref(OBJECT(cioc));
return TRUE;
}
Expand Down
8 changes: 8 additions & 0 deletions include/block/nbd.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "qemu-common.h"
#include "qemu/option.h"
#include "io/channel-socket.h"
#include "crypto/tlscreds.h"

struct nbd_request {
uint32_t magic;
Expand Down Expand Up @@ -56,7 +57,10 @@ struct nbd_reply {
#define NBD_REP_ACK (1) /* Data sending finished. */
#define NBD_REP_SERVER (2) /* Export description. */
#define NBD_REP_ERR_UNSUP ((UINT32_C(1) << 31) | 1) /* Unknown option. */
#define NBD_REP_ERR_POLICY ((UINT32_C(1) << 31) | 2) /* Server denied */
#define NBD_REP_ERR_INVALID ((UINT32_C(1) << 31) | 3) /* Invalid length. */
#define NBD_REP_ERR_TLS_REQD ((UINT32_C(1) << 31) | 5) /* TLS required */


#define NBD_CMD_MASK_COMMAND 0x0000ffff
#define NBD_CMD_FLAG_FUA (1 << 16)
Expand All @@ -81,6 +85,8 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc,
size_t length,
bool do_read);
int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags,
QCryptoTLSCreds *tlscreds, const char *hostname,
QIOChannel **outioc,
off_t *size, Error **errp);
int nbd_init(int fd, QIOChannelSocket *sioc, uint32_t flags, off_t size);
ssize_t nbd_send_request(QIOChannel *ioc, struct nbd_request *request);
Expand All @@ -106,6 +112,8 @@ void nbd_export_close_all(void);

void nbd_client_new(NBDExport *exp,
QIOChannelSocket *sioc,
QCryptoTLSCreds *tlscreds,
const char *tlsaclname,
void (*close)(NBDClient *));
void nbd_client_get(NBDClient *client);
void nbd_client_put(NBDClient *client);
Expand Down
136 changes: 135 additions & 1 deletion nbd/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,18 @@ static int nbd_handle_reply_err(uint32_t opt, uint32_t type, Error **errp)
error_setg(errp, "Unsupported option type %x", opt);
break;

case NBD_REP_ERR_POLICY:
error_setg(errp, "Denied by server for option %x", opt);
break;

case NBD_REP_ERR_INVALID:
error_setg(errp, "Invalid data length for option %x", opt);
break;

case NBD_REP_ERR_TLS_REQD:
error_setg(errp, "TLS negotiation required before option %x", opt);
break;

default:
error_setg(errp, "Unknown error code when asking for option %x", opt);
break;
Expand Down Expand Up @@ -242,17 +250,127 @@ static int nbd_receive_query_exports(QIOChannel *ioc,
return 0;
}

static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
QCryptoTLSCreds *tlscreds,
const char *hostname, Error **errp)
{
uint64_t magic = cpu_to_be64(NBD_OPTS_MAGIC);
uint32_t opt = cpu_to_be32(NBD_OPT_STARTTLS);
uint32_t length = 0;
uint32_t type;
QIOChannelTLS *tioc;
struct NBDTLSHandshakeData data = { 0 };

TRACE("Requesting TLS from server");
if (write_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
error_setg(errp, "Failed to send option magic");
return NULL;
}

if (write_sync(ioc, &opt, sizeof(opt)) != sizeof(opt)) {
error_setg(errp, "Failed to send option number");
return NULL;
}

if (write_sync(ioc, &length, sizeof(length)) != sizeof(length)) {
error_setg(errp, "Failed to send option length");
return NULL;
}

TRACE("Getting TLS reply from server1");
if (read_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
error_setg(errp, "failed to read option magic");
return NULL;
}
magic = be64_to_cpu(magic);
if (magic != NBD_REP_MAGIC) {
error_setg(errp, "Unexpected option magic");
return NULL;
}
TRACE("Getting TLS reply from server2");
if (read_sync(ioc, &opt, sizeof(opt)) != sizeof(opt)) {
error_setg(errp, "failed to read option");
return NULL;
}
opt = be32_to_cpu(opt);
if (opt != NBD_OPT_STARTTLS) {
error_setg(errp, "Unexpected option type %x expected %x",
opt, NBD_OPT_STARTTLS);
return NULL;
}

TRACE("Getting TLS reply from server");
if (read_sync(ioc, &type, sizeof(type)) != sizeof(type)) {
error_setg(errp, "failed to read option type");
return NULL;
}
type = be32_to_cpu(type);
if (type != NBD_REP_ACK) {
error_setg(errp, "Server rejected request to start TLS %x",
type);
return NULL;
}

TRACE("Getting TLS reply from server");
if (read_sync(ioc, &length, sizeof(length)) != sizeof(length)) {
error_setg(errp, "failed to read option length");
return NULL;
}
length = be32_to_cpu(length);
if (length != 0) {
error_setg(errp, "Start TLS reponse was not zero %x",
length);
return NULL;
}

TRACE("TLS request approved, setting up TLS");
tioc = qio_channel_tls_new_client(ioc, tlscreds, hostname, errp);
if (!tioc) {
return NULL;
}
data.loop = g_main_loop_new(g_main_context_default(), FALSE);
TRACE("Starting TLS hanshake");
qio_channel_tls_handshake(tioc,
nbd_tls_handshake,
&data,
NULL);

if (!data.complete) {
g_main_loop_run(data.loop);
}
g_main_loop_unref(data.loop);
if (data.error) {
error_propagate(errp, data.error);
object_unref(OBJECT(tioc));
return NULL;
}

return QIO_CHANNEL(tioc);
}


int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags,
QCryptoTLSCreds *tlscreds, const char *hostname,
QIOChannel **outioc,
off_t *size, Error **errp)
{
char buf[256];
uint64_t magic, s;
int rc;

TRACE("Receiving negotiation.");
TRACE("Receiving negotiation tlscreds=%p hostname=%s.",
tlscreds, hostname ? hostname : "<null>");

rc = -EINVAL;

if (outioc) {
*outioc = NULL;
}
if (tlscreds && !outioc) {
error_setg(errp, "Output I/O channel required for TLS");
goto fail;
}

if (read_sync(ioc, buf, 8) != 8) {
error_setg(errp, "Failed to read data");
goto fail;
Expand Down Expand Up @@ -314,6 +432,18 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags,
error_setg(errp, "Failed to send clientflags field");
goto fail;
}
if (tlscreds) {
if (fixedNewStyle) {
*outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp);
if (!*outioc) {
goto fail;
}
ioc = *outioc;
} else {
error_setg(errp, "Server does not support STARTTLS");
goto fail;
}
}
if (!name) {
TRACE("Using default NBD export name \"\"");
name = "";
Expand Down Expand Up @@ -371,6 +501,10 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags,
error_setg(errp, "Server does not support export names");
goto fail;
}
if (tlscreds) {
error_setg(errp, "Server does not support STARTTLS");
goto fail;
}

if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) {
error_setg(errp, "Failed to read export length");
Expand Down
15 changes: 15 additions & 0 deletions nbd/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,18 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc,
g_free(local_iov_head);
return done;
}


void nbd_tls_handshake(Object *src,
Error *err,
void *opaque)
{
struct NBDTLSHandshakeData *data = opaque;

if (err) {
TRACE("TLS failed %s", error_get_pretty(err));
data->error = error_copy(err);
}
data->complete = true;
g_main_loop_quit(data->loop);
}
14 changes: 14 additions & 0 deletions nbd/nbd-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#define NBD_INTERNAL_H
#include "block/nbd.h"
#include "sysemu/block-backend.h"
#include "io/channel-tls.h"

#include "qemu/coroutine.h"
#include "qemu/iov.h"
Expand Down Expand Up @@ -78,6 +79,8 @@
#define NBD_OPT_EXPORT_NAME (1)
#define NBD_OPT_ABORT (2)
#define NBD_OPT_LIST (3)
#define NBD_OPT_PEEK_EXPORT (4)
#define NBD_OPT_STARTTLS (5)

/* NBD errors are based on errno numbers, so there is a 1:1 mapping,
* but only a limited set of errno values is specified in the protocol.
Expand Down Expand Up @@ -108,4 +111,15 @@ static inline ssize_t write_sync(QIOChannel *ioc, void *buffer, size_t size)
return nbd_wr_syncv(ioc, &iov, 1, 0, size, false);
}

struct NBDTLSHandshakeData {
GMainLoop *loop;
bool complete;
Error *error;
};


void nbd_tls_handshake(Object *src,
Error *err,
void *opaque);

#endif
Loading

0 comments on commit f95910f

Please sign in to comment.