diff options
Diffstat (limited to 'nbd/server.c')
-rw-r--r-- | nbd/server.c | 292 |
1 files changed, 203 insertions, 89 deletions
diff --git a/nbd/server.c b/nbd/server.c index 36bcafcd50..5b76261666 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2016 Red Hat, Inc. * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * * Network Block Device Server Side @@ -38,6 +39,8 @@ static int system_errno_to_nbd_errno(int err) case EFBIG: case ENOSPC: return NBD_ENOSPC; + case ESHUTDOWN: + return NBD_ESHUTDOWN; case EINVAL: default: return NBD_EINVAL; @@ -46,10 +49,10 @@ static int system_errno_to_nbd_errno(int err) /* Definitions for opaque data types */ -typedef struct NBDRequest NBDRequest; +typedef struct NBDRequestData NBDRequestData; -struct NBDRequest { - QSIMPLEQ_ENTRY(NBDRequest) entry; +struct NBDRequestData { + QSIMPLEQ_ENTRY(NBDRequestData) entry; NBDClient *client; uint8_t *data; bool complete; @@ -61,6 +64,7 @@ struct NBDExport { BlockBackend *blk; char *name; + char *description; off_t dev_offset; off_t size; uint16_t nbdflags; @@ -79,6 +83,7 @@ struct NBDClient { int refcount; void (*close)(NBDClient *client); + bool no_zeroes; NBDExport *exp; QCryptoTLSCreds *tlscreds; char *tlsaclname; @@ -129,7 +134,8 @@ static ssize_t nbd_negotiate_read(QIOChannel *ioc, void *buffer, size_t size) } -static ssize_t nbd_negotiate_write(QIOChannel *ioc, void *buffer, size_t size) +static ssize_t nbd_negotiate_write(QIOChannel *ioc, const void *buffer, + size_t size) { ssize_t ret; guint watch; @@ -193,12 +199,15 @@ static ssize_t nbd_negotiate_drop_sync(QIOChannel *ioc, size_t size) */ -static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt) +/* Send a reply header, including length, but no payload. + * Return -errno on error, 0 on success. */ +static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type, + uint32_t opt, uint32_t len) { uint64_t magic; - uint32_t len; - TRACE("Reply opt=%" PRIx32 " type=%" PRIx32, type, opt); + TRACE("Reply opt=%" PRIx32 " type=%" PRIx32 " len=%" PRIu32, + type, opt, len); magic = cpu_to_be64(NBD_REP_MAGIC); if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) != sizeof(magic)) { @@ -215,7 +224,7 @@ static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt) LOG("write failed (rep type)"); return -EINVAL; } - len = cpu_to_be32(0); + len = cpu_to_be32(len); if (nbd_negotiate_write(ioc, &len, sizeof(len)) != sizeof(len)) { LOG("write failed (rep data length)"); return -EINVAL; @@ -223,45 +232,82 @@ static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt) return 0; } -static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp) +/* Send a reply header with default 0 length. + * Return -errno on error, 0 on success. */ +static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt) { - uint64_t magic, name_len; - uint32_t opt, type, len; + return nbd_negotiate_send_rep_len(ioc, type, opt, 0); +} - TRACE("Advertising export name '%s'", exp->name ? exp->name : ""); - name_len = strlen(exp->name); - magic = cpu_to_be64(NBD_REP_MAGIC); - if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) != sizeof(magic)) { - LOG("write failed (magic)"); - return -EINVAL; - } - opt = cpu_to_be32(NBD_OPT_LIST); - if (nbd_negotiate_write(ioc, &opt, sizeof(opt)) != sizeof(opt)) { - LOG("write failed (opt)"); - return -EINVAL; +/* Send an error reply. + * Return -errno on error, 0 on success. */ +static int GCC_FMT_ATTR(4, 5) +nbd_negotiate_send_rep_err(QIOChannel *ioc, uint32_t type, + uint32_t opt, const char *fmt, ...) +{ + va_list va; + char *msg; + int ret; + size_t len; + + va_start(va, fmt); + msg = g_strdup_vprintf(fmt, va); + va_end(va); + len = strlen(msg); + assert(len < 4096); + TRACE("sending error message \"%s\"", msg); + ret = nbd_negotiate_send_rep_len(ioc, type, opt, len); + if (ret < 0) { + goto out; } - type = cpu_to_be32(NBD_REP_SERVER); - if (nbd_negotiate_write(ioc, &type, sizeof(type)) != sizeof(type)) { - LOG("write failed (reply type)"); - return -EINVAL; + if (nbd_negotiate_write(ioc, msg, len) != len) { + LOG("write failed (error message)"); + ret = -EIO; + } else { + ret = 0; } - len = cpu_to_be32(name_len + sizeof(len)); - if (nbd_negotiate_write(ioc, &len, sizeof(len)) != sizeof(len)) { - LOG("write failed (length)"); - return -EINVAL; +out: + g_free(msg); + return ret; +} + +/* Send a single NBD_REP_SERVER reply to NBD_OPT_LIST, including payload. + * Return -errno on error, 0 on success. */ +static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp) +{ + size_t name_len, desc_len; + uint32_t len; + const char *name = exp->name ? exp->name : ""; + const char *desc = exp->description ? exp->description : ""; + int rc; + + TRACE("Advertising export name '%s' description '%s'", name, desc); + name_len = strlen(name); + desc_len = strlen(desc); + len = name_len + desc_len + sizeof(len); + rc = nbd_negotiate_send_rep_len(ioc, NBD_REP_SERVER, NBD_OPT_LIST, len); + if (rc < 0) { + return rc; } + len = cpu_to_be32(name_len); if (nbd_negotiate_write(ioc, &len, sizeof(len)) != sizeof(len)) { - LOG("write failed (length)"); + LOG("write failed (name length)"); + return -EINVAL; + } + if (nbd_negotiate_write(ioc, name, name_len) != name_len) { + LOG("write failed (name buffer)"); return -EINVAL; } - if (nbd_negotiate_write(ioc, exp->name, name_len) != name_len) { - LOG("write failed (buffer)"); + if (nbd_negotiate_write(ioc, desc, desc_len) != desc_len) { + LOG("write failed (description buffer)"); return -EINVAL; } return 0; } +/* Process the NBD_OPT_LIST command, with a potential series of replies. + * Return -errno on error, 0 on success. */ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length) { NBDExport *exp; @@ -270,8 +316,9 @@ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length) if (nbd_negotiate_drop_sync(client->ioc, length) != length) { return -EIO; } - return nbd_negotiate_send_rep(client->ioc, - NBD_REP_ERR_INVALID, NBD_OPT_LIST); + return nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_INVALID, NBD_OPT_LIST, + "OPT_LIST should not have length"); } /* For each export, send a NBD_REP_SERVER reply. */ @@ -318,7 +365,8 @@ fail: return rc; } - +/* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the + * new channel for all further (now-encrypted) communication. */ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, uint32_t length) { @@ -332,7 +380,8 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, if (nbd_negotiate_drop_sync(ioc, length) != length) { return NULL; } - nbd_negotiate_send_rep(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS); + nbd_negotiate_send_rep_err(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS, + "OPT_STARTTLS should not have length"); return NULL; } @@ -371,6 +420,8 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, } +/* Process all NBD_OPT_* client option commands. + * Return -errno on error, 0 on success. */ static int nbd_negotiate_options(NBDClient *client) { uint32_t flags; @@ -402,6 +453,11 @@ static int nbd_negotiate_options(NBDClient *client) fixedNewstyle = true; flags &= ~NBD_FLAG_C_FIXED_NEWSTYLE; } + if (flags & NBD_FLAG_C_NO_ZEROES) { + TRACE("Client supports no zeroes at handshake end"); + client->no_zeroes = true; + flags &= ~NBD_FLAG_C_NO_ZEROES; + } if (flags != 0) { TRACE("Unknown client flags 0x%" PRIx32 " received", flags); return -EIO; @@ -461,16 +517,22 @@ static int nbd_negotiate_options(NBDClient *client) return -EINVAL; default: - TRACE("Option 0x%" PRIx32 " not permitted before TLS", - clientflags); if (nbd_negotiate_drop_sync(client->ioc, length) != length) { return -EIO; } - ret = nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_TLS_REQD, - clientflags); + ret = nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_TLS_REQD, + clientflags, + "Option 0x%" PRIx32 + "not permitted before TLS", + clientflags); if (ret < 0) { return ret; } + /* Let the client keep trying, unless they asked to quit */ + if (clientflags == NBD_OPT_ABORT) { + return -EINVAL; + } break; } } else if (fixedNewstyle) { @@ -483,6 +545,10 @@ static int nbd_negotiate_options(NBDClient *client) break; case NBD_OPT_ABORT: + /* NBD spec says we must try to reply before + * disconnecting, but that we must also tolerate + * guests that don't wait for our reply. */ + nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, clientflags); return -EINVAL; case NBD_OPT_EXPORT_NAME: @@ -493,27 +559,30 @@ static int nbd_negotiate_options(NBDClient *client) return -EIO; } if (client->tlscreds) { - TRACE("TLS already enabled"); - ret = nbd_negotiate_send_rep(client->ioc, - NBD_REP_ERR_INVALID, - clientflags); + ret = nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_INVALID, + clientflags, + "TLS already enabled"); } else { - TRACE("TLS not configured"); - ret = nbd_negotiate_send_rep(client->ioc, - NBD_REP_ERR_POLICY, - clientflags); + ret = nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_POLICY, + clientflags, + "TLS not configured"); } if (ret < 0) { return ret; } break; default: - TRACE("Unsupported option 0x%" PRIx32, clientflags); if (nbd_negotiate_drop_sync(client->ioc, length) != length) { return -EIO; } - ret = nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_UNSUP, - clientflags); + ret = nbd_negotiate_send_rep_err(client->ioc, + NBD_REP_ERR_UNSUP, + clientflags, + "Unsupported option 0x%" + PRIx32, + clientflags); if (ret < 0) { return ret; } @@ -547,8 +616,10 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) char buf[8 + 8 + 8 + 128]; int rc; const uint16_t myflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_TRIM | - NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA); + NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA | + NBD_FLAG_SEND_WRITE_ZEROES); bool oldStyle; + size_t len; /* Old style negotiation header without options [ 0 .. 7] passwd ("NBDMAGIC") @@ -565,7 +636,7 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) ....options sent.... [18 .. 25] size [26 .. 27] export flags - [28 .. 151] reserved (0) + [28 .. 151] reserved (0, omit if no_zeroes) */ qio_channel_set_blocking(client->ioc, false, NULL); @@ -584,7 +655,7 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) stw_be_p(buf + 26, client->exp->nbdflags | myflags); } else { stq_be_p(buf + 8, NBD_OPTS_MAGIC); - stw_be_p(buf + 16, NBD_FLAG_FIXED_NEWSTYLE); + stw_be_p(buf + 16, NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES); } if (oldStyle) { @@ -611,8 +682,8 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) client->exp->size, client->exp->nbdflags | myflags); stq_be_p(buf + 18, client->exp->size); stw_be_p(buf + 26, client->exp->nbdflags | myflags); - if (nbd_negotiate_write(client->ioc, buf + 18, sizeof(buf) - 18) != - sizeof(buf) - 18) { + len = client->no_zeroes ? 10 : sizeof(buf) - 18; + if (nbd_negotiate_write(client->ioc, buf + 18, len) != len) { LOG("write failed"); goto fail; } @@ -624,7 +695,7 @@ fail: return rc; } -static ssize_t nbd_receive_request(QIOChannel *ioc, struct nbd_request *request) +static ssize_t nbd_receive_request(QIOChannel *ioc, NBDRequest *request) { uint8_t buf[NBD_REQUEST_SIZE]; uint32_t magic; @@ -642,21 +713,23 @@ static ssize_t nbd_receive_request(QIOChannel *ioc, struct nbd_request *request) /* Request [ 0 .. 3] magic (NBD_REQUEST_MAGIC) - [ 4 .. 7] type (0 == READ, 1 == WRITE) + [ 4 .. 5] flags (NBD_CMD_FLAG_FUA, ...) + [ 6 .. 7] type (NBD_CMD_READ, ...) [ 8 .. 15] handle [16 .. 23] from [24 .. 27] len */ magic = ldl_be_p(buf); - request->type = ldl_be_p(buf + 4); + request->flags = lduw_be_p(buf + 4); + request->type = lduw_be_p(buf + 6); request->handle = ldq_be_p(buf + 8); request->from = ldq_be_p(buf + 16); request->len = ldl_be_p(buf + 24); - TRACE("Got request: { magic = 0x%" PRIx32 ", .type = %" PRIx32 - ", from = %" PRIu64 " , len = %" PRIu32 " }", - magic, request->type, request->from, request->len); + TRACE("Got request: { magic = 0x%" PRIx32 ", .flags = %" PRIx16 + ", .type = %" PRIx16 ", from = %" PRIu64 ", len = %" PRIu32 " }", + magic, request->flags, request->type, request->from, request->len); if (magic != NBD_REQUEST_MAGIC) { LOG("invalid magic (got 0x%" PRIx32 ")", magic); @@ -665,7 +738,7 @@ static ssize_t nbd_receive_request(QIOChannel *ioc, struct nbd_request *request) return 0; } -static ssize_t nbd_send_reply(QIOChannel *ioc, struct nbd_reply *reply) +static ssize_t nbd_send_reply(QIOChannel *ioc, NBDReply *reply) { uint8_t buf[NBD_REPLY_SIZE]; ssize_t ret; @@ -747,21 +820,21 @@ static void client_close(NBDClient *client) } } -static NBDRequest *nbd_request_get(NBDClient *client) +static NBDRequestData *nbd_request_get(NBDClient *client) { - NBDRequest *req; + NBDRequestData *req; assert(client->nb_requests <= MAX_NBD_REQUESTS - 1); client->nb_requests++; nbd_update_can_read(client); - req = g_new0(NBDRequest, 1); + req = g_new0(NBDRequestData, 1); nbd_client_get(client); req->client = client; return req; } -static void nbd_request_put(NBDRequest *req) +static void nbd_request_put(NBDRequestData *req) { NBDClient *client = req->client; @@ -894,6 +967,12 @@ void nbd_export_set_name(NBDExport *exp, const char *name) nbd_export_put(exp); } +void nbd_export_set_description(NBDExport *exp, const char *description) +{ + g_free(exp->description); + exp->description = g_strdup(description); +} + void nbd_export_close(NBDExport *exp) { NBDClient *client, *next; @@ -903,6 +982,7 @@ void nbd_export_close(NBDExport *exp) client_close(client); } nbd_export_set_name(exp, NULL); + nbd_export_set_description(exp, NULL); nbd_export_put(exp); } @@ -921,6 +1001,7 @@ void nbd_export_put(NBDExport *exp) if (--exp->refcount == 0) { assert(exp->name == NULL); + assert(exp->description == NULL); if (exp->close) { exp->close(exp); @@ -955,7 +1036,7 @@ void nbd_export_close_all(void) } } -static ssize_t nbd_co_send_reply(NBDRequest *req, struct nbd_reply *reply, +static ssize_t nbd_co_send_reply(NBDRequestData *req, NBDReply *reply, int len) { NBDClient *client = req->client; @@ -991,11 +1072,10 @@ static ssize_t nbd_co_send_reply(NBDRequest *req, struct nbd_reply *reply, * and any other negative value to report an error to the client * (although the caller may still need to disconnect after reporting * the error). */ -static ssize_t nbd_co_receive_request(NBDRequest *req, - struct nbd_request *request) +static ssize_t nbd_co_receive_request(NBDRequestData *req, + NBDRequest *request) { NBDClient *client = req->client; - uint32_t command; ssize_t rc; g_assert(qemu_in_coroutine()); @@ -1012,13 +1092,12 @@ static ssize_t nbd_co_receive_request(NBDRequest *req, TRACE("Decoding type"); - command = request->type & NBD_CMD_MASK_COMMAND; - if (command != NBD_CMD_WRITE) { + if (request->type != NBD_CMD_WRITE) { /* No payload, we are ready to read the next request. */ req->complete = true; } - if (command == NBD_CMD_DISC) { + if (request->type == NBD_CMD_DISC) { /* Special case: we're going to disconnect without a reply, * whether or not flags, from, or len are bogus */ TRACE("Request type is DISCONNECT"); @@ -1035,7 +1114,7 @@ static ssize_t nbd_co_receive_request(NBDRequest *req, goto out; } - if (command == NBD_CMD_READ || command == NBD_CMD_WRITE) { + if (request->type == NBD_CMD_READ || request->type == NBD_CMD_WRITE) { if (request->len > NBD_MAX_BUFFER_SIZE) { LOG("len (%" PRIu32" ) is larger than max len (%u)", request->len, NBD_MAX_BUFFER_SIZE); @@ -1049,7 +1128,7 @@ static ssize_t nbd_co_receive_request(NBDRequest *req, goto out; } } - if (command == NBD_CMD_WRITE) { + if (request->type == NBD_CMD_WRITE) { TRACE("Reading %" PRIu32 " byte(s)", request->len); if (read_sync(client->ioc, req->data, request->len) != request->len) { @@ -1065,12 +1144,17 @@ static ssize_t nbd_co_receive_request(NBDRequest *req, LOG("operation past EOF; From: %" PRIu64 ", Len: %" PRIu32 ", Size: %" PRIu64, request->from, request->len, (uint64_t)client->exp->size); - rc = command == NBD_CMD_WRITE ? -ENOSPC : -EINVAL; + rc = request->type == NBD_CMD_WRITE ? -ENOSPC : -EINVAL; + goto out; + } + if (request->flags & ~(NBD_CMD_FLAG_FUA | NBD_CMD_FLAG_NO_HOLE)) { + LOG("unsupported flags (got 0x%x)", request->flags); + rc = -EINVAL; goto out; } - if (request->type & ~NBD_CMD_MASK_COMMAND & ~NBD_CMD_FLAG_FUA) { - LOG("unsupported flags (got 0x%x)", - request->type & ~NBD_CMD_MASK_COMMAND); + if (request->type != NBD_CMD_WRITE_ZEROES && + (request->flags & NBD_CMD_FLAG_NO_HOLE)) { + LOG("unexpected flags (got 0x%x)", request->flags); rc = -EINVAL; goto out; } @@ -1088,11 +1172,10 @@ static void nbd_trip(void *opaque) { NBDClient *client = opaque; NBDExport *exp = client->exp; - NBDRequest *req; - struct nbd_request request; - struct nbd_reply reply; + NBDRequestData *req; + NBDRequest request; + NBDReply reply; ssize_t ret; - uint32_t command; int flags; TRACE("Reading request."); @@ -1116,7 +1199,6 @@ static void nbd_trip(void *opaque) reply.error = -ret; goto error_reply; } - command = request.type & NBD_CMD_MASK_COMMAND; if (client->closing) { /* @@ -1126,11 +1208,12 @@ static void nbd_trip(void *opaque) goto done; } - switch (command) { + switch (request.type) { case NBD_CMD_READ: TRACE("Request type is READ"); - if (request.type & NBD_CMD_FLAG_FUA) { + /* XXX: NBD Protocol only documents use of FUA with WRITE */ + if (request.flags & NBD_CMD_FLAG_FUA) { ret = blk_co_flush(exp->blk); if (ret < 0) { LOG("flush failed"); @@ -1163,7 +1246,7 @@ static void nbd_trip(void *opaque) TRACE("Writing to device"); flags = 0; - if (request.type & NBD_CMD_FLAG_FUA) { + if (request.flags & NBD_CMD_FLAG_FUA) { flags |= BDRV_REQ_FUA; } ret = blk_pwrite(exp->blk, request.from + exp->dev_offset, @@ -1179,6 +1262,37 @@ static void nbd_trip(void *opaque) } break; + case NBD_CMD_WRITE_ZEROES: + TRACE("Request type is WRITE_ZEROES"); + + if (exp->nbdflags & NBD_FLAG_READ_ONLY) { + TRACE("Server is read-only, return error"); + reply.error = EROFS; + goto error_reply; + } + + TRACE("Writing to device"); + + flags = 0; + if (request.flags & NBD_CMD_FLAG_FUA) { + flags |= BDRV_REQ_FUA; + } + if (!(request.flags & NBD_CMD_FLAG_NO_HOLE)) { + flags |= BDRV_REQ_MAY_UNMAP; + } + ret = blk_pwrite_zeroes(exp->blk, request.from + exp->dev_offset, + request.len, flags); + if (ret < 0) { + LOG("writing to file failed"); + reply.error = -ret; + goto error_reply; + } + + if (nbd_co_send_reply(req, &reply, 0) < 0) { + goto out; + } + break; + case NBD_CMD_DISC: /* unreachable, thanks to special case in nbd_co_receive_request() */ abort(); |