From nobody Sun Feb 8 13:39:25 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1487645139565486.51266220557216; Mon, 20 Feb 2017 18:45:39 -0800 (PST) Received: from localhost ([::1]:41889 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cg0SL-0006q5-5b for importer@patchew.org; Mon, 20 Feb 2017 21:45:33 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:40827) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cg0Q2-0005ZY-P8 for qemu-devel@nongnu.org; Mon, 20 Feb 2017 21:43:12 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cg0Q1-0003XG-6z for qemu-devel@nongnu.org; Mon, 20 Feb 2017 21:43:10 -0500 Received: from mx1.redhat.com ([209.132.183.28]:52002) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cg0Pv-0003Rr-Dg; Mon, 20 Feb 2017 21:43:03 -0500 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 8003C80F97; Tue, 21 Feb 2017 02:43:03 +0000 (UTC) Received: from red.redhat.com (ovpn-123-67.rdu2.redhat.com [10.10.123.67] (may be forged)) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v1L2gnuE023090; Mon, 20 Feb 2017 21:43:02 -0500 From: Eric Blake To: qemu-devel@nongnu.org Date: Mon, 20 Feb 2017 20:42:45 -0600 Message-Id: <20170221024248.11027-6-eblake@redhat.com> In-Reply-To: <20170221024248.11027-1-eblake@redhat.com> References: <20170221024248.11027-1-eblake@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.27 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Tue, 21 Feb 2017 02:43:03 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [PATCH v4 5/8] nbd: Implement NBD_OPT_GO on server X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: pbonzini@redhat.com, vsementsov@virtuozzo.com, den@virtuozzo.com, qemu-block@nongnu.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" NBD_OPT_EXPORT_NAME is lousy: per the NBD protocol, any failure requires us to close the connection rather than report an error. Therefore, upstream NBD recently added NBD_OPT_GO as the improved version of the option that does what we want [1], along with NBD_OPT_INFO that returns the same information but does not transition to transmission phase. [1] https://github.com/NetworkBlockDevice/nbd/blob/extension-info/doc/proto= .md This is a first cut at the information types, and only passes the same information already available through NBD_OPT_LIST and NBD_OPT_EXPORT_NAME; items like NBD_INFO_BLOCK_SIZE (and thus any use of NBD_REP_ERR_BLOCK_SIZE_REQD) are intentionally left for later patches. Signed-off-by: Eric Blake --- v4: revamp to another round of NBD protocol changes v3: revamp to match latest version of NBD protocol --- nbd/server.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++= ---- 1 file changed, 195 insertions(+), 13 deletions(-) diff --git a/nbd/server.c b/nbd/server.c index 767ca0f..3b1a4a5 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -209,6 +209,7 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, = uint32_t type, TRACE("Reply opt=3D%" PRIx32 " (%s), type=3D%" PRIx32 " (%s), len=3D%"= PRIu32, opt, nbd_opt_lookup(opt), type, nbd_rep_lookup(type), len); + assert(len < NBD_MAX_BUFFER_SIZE); magic =3D cpu_to_be64(NBD_REP_MAGIC); if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) !=3D sizeof(magic)= ) { LOG("write failed (rep magic)"); @@ -331,6 +332,8 @@ static int nbd_negotiate_handle_list(NBDClient *client,= uint32_t length) return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST); } +/* Send a reply to NBD_OPT_EXPORT_NAME. + * Return -errno on error, 0 on success. */ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t le= ngth) { int rc =3D -EINVAL; @@ -365,6 +368,171 @@ fail: return rc; } +/* Send a single NBD_REP_INFO, with a buffer @buf of @length bytes. + * The buffer does NOT include the info type prefix. + * Return -errno on error, 0 if ready to send more. */ +static int nbd_negotiate_send_info(NBDClient *client, uint32_t opt, + uint16_t info, uint32_t length, void *b= uf) +{ + int rc; + + TRACE("Sending NBD_REP_INFO type %" PRIu16 " (%s) with remaining lengt= h %" + PRIu32, info, nbd_info_lookup(info), length); + rc =3D nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt, + sizeof(info) + length); + if (rc < 0) { + return rc; + } + cpu_to_be16s(&info); + if (nbd_negotiate_write(client->ioc, &info, sizeof(info)) !=3D + sizeof(info)) { + LOG("write failed"); + return -EIO; + } + if (nbd_negotiate_write(client->ioc, buf, length) !=3D length) { + LOG("write failed"); + return -EIO; + } + return 0; +} + +/* Handle NBD_OPT_INFO and NBD_OPT_GO. + * Return -errno on error, 0 if ready for next option, and 1 to move + * into transmission phase. */ +static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, + uint32_t opt, uint16_t myflags) +{ + int rc; + char name[NBD_MAX_NAME_SIZE + 1]; + NBDExport *exp; + uint16_t requests; + uint16_t request; + uint32_t namelen; + bool sendname =3D false; + char buf[sizeof(uint64_t) + sizeof(uint16_t)]; + const char *msg; + + /* Client sends: + 2 bytes: N, number of requests (can be 0) + N * 2 bytes: N requests + 4 bytes: L, name length (can be 0) + L bytes: export name + */ + if (length < sizeof(requests) + sizeof(namelen)) { + msg =3D "overall request too short"; + goto invalid; + } + if (nbd_negotiate_read(client->ioc, &requests, sizeof(requests)) !=3D + sizeof(requests)) { + LOG("read failed"); + return -EIO; + } + be16_to_cpus(&requests); + length -=3D sizeof(requests); + TRACE("Client requested %d items of info", requests); + if (requests > (length - sizeof(namelen)) / sizeof(request)) { + msg =3D "too many requests for overall length"; + goto invalid; + } + while (requests--) { + if (nbd_negotiate_read(client->ioc, &request, sizeof(request)) != =3D + sizeof(request)) { + LOG("read failed"); + return -EIO; + } + be16_to_cpus(&request); + length -=3D sizeof(request); + TRACE("Client requested info %d (%s)", request, + nbd_info_lookup(request)); + /* For now, we only care about NBD_INFO_NAME; everything else + * is either a request we don't know or something we send + * regardless of request. */ + if (request =3D=3D NBD_INFO_NAME) { + sendname =3D true; + } + } + + if (nbd_negotiate_read(client->ioc, &namelen, sizeof(namelen)) !=3D + sizeof(namelen)) { + LOG("read failed"); + return -EIO; + } + be32_to_cpus(&namelen); + length -=3D sizeof(namelen); + TRACE("Client requested namelen %u", namelen); + if (length !=3D namelen || namelen > sizeof(name)) { + msg =3D "name too long"; + goto invalid; + } + if (nbd_negotiate_read(client->ioc, name, length) !=3D length) { + LOG("read failed"); + return -EIO; + } + name[length] =3D '\0'; + + TRACE("Client requested info on export '%s'", name); + + exp =3D nbd_export_find(name); + if (!exp) { + return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN, + opt, "export '%s' not present", + name); + } + + /* Don't bother sending NBD_INFO_NAME unless client requested it */ + if (sendname) { + rc =3D nbd_negotiate_send_info(client, opt, NBD_INFO_NAME, length,= name); + if (rc < 0) { + return rc; + } + } + + /* Send NBD_INFO_DESCRIPTION only if available, regardless of + * client request */ + if (exp->description) { + size_t len =3D strlen(exp->description); + + rc =3D nbd_negotiate_send_info(client, opt, NBD_INFO_DESCRIPTION, + len, exp->description); + if (rc < 0) { + return rc; + } + } + + /* Send NBD_INFO_EXPORT always */ + TRACE("advertising size %" PRIu64 " and flags %" PRIx16, + exp->size, exp->nbdflags | myflags); + stq_be_p(buf, exp->size); + stw_be_p(buf + 8, exp->nbdflags | myflags); + rc =3D nbd_negotiate_send_info(client, opt, NBD_INFO_EXPORT, + sizeof(buf), buf); + if (rc < 0) { + return rc; + } + + /* Final reply */ + rc =3D nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt); + if (rc < 0) { + return rc; + } + + if (opt =3D=3D NBD_OPT_GO) { + client->exp =3D exp; + QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); + nbd_export_get(client->exp); + rc =3D 1; + } + return rc; + + invalid: + if (nbd_negotiate_drop_sync(client->ioc, length) !=3D length) { + return -EIO; + } + return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, op= t, + "%s", msg); +} + + /* 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, @@ -420,9 +588,10 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDCl= ient *client, } -/* Process all NBD_OPT_* client option commands. - * Return -errno on error, 0 on success. */ -static int nbd_negotiate_options(NBDClient *client) +/* Process all NBD_OPT_* client option commands, during fixed newstyle + * negotiation. Return -errno on error, 0 on successful NBD_OPT_EXPORT_NAM= E, + * and 1 on successful NBD_OPT_GO. */ +static int nbd_negotiate_options(NBDClient *client, uint16_t myflags) { uint32_t flags; bool fixedNewstyle =3D false; @@ -555,6 +724,16 @@ static int nbd_negotiate_options(NBDClient *client) case NBD_OPT_EXPORT_NAME: return nbd_negotiate_handle_export_name(client, length); + case NBD_OPT_INFO: + case NBD_OPT_GO: + ret =3D nbd_negotiate_handle_info(client, length, clientfl= ags, + myflags); + if (ret) { + assert(ret < 0 || clientflags =3D=3D NBD_OPT_GO); + return ret; + } + break; + case NBD_OPT_STARTTLS: if (nbd_negotiate_drop_sync(client->ioc, length) !=3D leng= th) { return -EIO; @@ -675,20 +854,23 @@ static coroutine_fn int nbd_negotiate(NBDClientNewDat= a *data) LOG("write failed"); goto fail; } - rc =3D nbd_negotiate_options(client); - if (rc !=3D 0) { + rc =3D nbd_negotiate_options(client, myflags); + if (rc < 0) { LOG("option negotiation failed"); goto fail; } - TRACE("advertising size %" PRIu64 " and flags %x", - client->exp->size, client->exp->nbdflags | myflags); - stq_be_p(buf + 18, client->exp->size); - stw_be_p(buf + 26, client->exp->nbdflags | myflags); - len =3D client->no_zeroes ? 10 : sizeof(buf) - 18; - if (nbd_negotiate_write(client->ioc, buf + 18, len) !=3D len) { - LOG("write failed"); - goto fail; + if (!rc) { + /* If options ended with NBD_OPT_GO, we already sent this. */ + TRACE("advertising size %" PRIu64 " and flags %x", + client->exp->size, client->exp->nbdflags | myflags); + stq_be_p(buf + 18, client->exp->size); + stw_be_p(buf + 26, client->exp->nbdflags | myflags); + len =3D client->no_zeroes ? 10 : sizeof(buf) - 18; + if (nbd_negotiate_write(client->ioc, buf + 18, len) !=3D len) { + LOG("write failed"); + goto fail; + } } } --=20 2.9.3