qemu/blockdev-nbd.c
<<
>>
Prefs
   1/*
   2 * Serving QEMU block devices via NBD
   3 *
   4 * Copyright (c) 2012 Red Hat, Inc.
   5 *
   6 * Author: Paolo Bonzini <pbonzini@redhat.com>
   7 *
   8 * This work is licensed under the terms of the GNU GPL, version 2 or
   9 * later.  See the COPYING file in the top-level directory.
  10 */
  11
  12#include "qemu/osdep.h"
  13#include "system/blockdev.h"
  14#include "system/block-backend.h"
  15#include "hw/block/block.h"
  16#include "qapi/error.h"
  17#include "qapi/clone-visitor.h"
  18#include "qapi/qapi-visit-block-export.h"
  19#include "qapi/qapi-commands-block-export.h"
  20#include "block/nbd.h"
  21#include "io/channel-socket.h"
  22#include "io/net-listener.h"
  23
  24typedef struct NBDConn {
  25    QIOChannelSocket *cioc;
  26    QLIST_ENTRY(NBDConn) next;
  27} NBDConn;
  28
  29typedef struct NBDServerData {
  30    QIONetListener *listener;
  31    uint32_t handshake_max_secs;
  32    QCryptoTLSCreds *tlscreds;
  33    char *tlsauthz;
  34    uint32_t max_connections;
  35    uint32_t connections;
  36    QLIST_HEAD(, NBDConn) conns;
  37} NBDServerData;
  38
  39static NBDServerData *nbd_server;
  40static int qemu_nbd_connections = -1; /* Non-negative if this is qemu-nbd */
  41
  42static void nbd_update_server_watch(NBDServerData *s);
  43
  44void nbd_server_is_qemu_nbd(int max_connections)
  45{
  46    qemu_nbd_connections = max_connections;
  47}
  48
  49bool nbd_server_is_running(void)
  50{
  51    return nbd_server || qemu_nbd_connections >= 0;
  52}
  53
  54int nbd_server_max_connections(void)
  55{
  56    return nbd_server ? nbd_server->max_connections : qemu_nbd_connections;
  57}
  58
  59static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
  60{
  61    NBDConn *conn = nbd_client_owner(client);
  62
  63    assert(qemu_in_main_thread() && nbd_server);
  64
  65    object_unref(OBJECT(conn->cioc));
  66    QLIST_REMOVE(conn, next);
  67    g_free(conn);
  68
  69    nbd_client_put(client);
  70    assert(nbd_server->connections > 0);
  71    nbd_server->connections--;
  72    nbd_update_server_watch(nbd_server);
  73}
  74
  75static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
  76                       gpointer opaque)
  77{
  78    NBDConn *conn = g_new0(NBDConn, 1);
  79
  80    assert(qemu_in_main_thread() && nbd_server);
  81    nbd_server->connections++;
  82    object_ref(OBJECT(cioc));
  83    conn->cioc = cioc;
  84    QLIST_INSERT_HEAD(&nbd_server->conns, conn, next);
  85    nbd_update_server_watch(nbd_server);
  86
  87    qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
  88    nbd_client_new(cioc, nbd_server->handshake_max_secs,
  89                   nbd_server->tlscreds, nbd_server->tlsauthz,
  90                   nbd_blockdev_client_closed, conn);
  91}
  92
  93static void nbd_update_server_watch(NBDServerData *s)
  94{
  95    if (s->listener) {
  96        if (!s->max_connections || s->connections < s->max_connections) {
  97            qio_net_listener_set_client_func(s->listener, nbd_accept, NULL,
  98                                             NULL);
  99        } else {
 100            qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL);
 101        }
 102    }
 103}
 104
 105static void nbd_server_free(NBDServerData *server)
 106{
 107    NBDConn *conn, *tmp;
 108
 109    if (!server) {
 110        return;
 111    }
 112
 113    /*
 114     * Forcefully close the listener socket, and any clients that have
 115     * not yet disconnected on their own.
 116     */
 117    qio_net_listener_disconnect(server->listener);
 118    object_unref(OBJECT(server->listener));
 119    server->listener = NULL;
 120    QLIST_FOREACH_SAFE(conn, &server->conns, next, tmp) {
 121        qio_channel_shutdown(QIO_CHANNEL(conn->cioc), QIO_CHANNEL_SHUTDOWN_BOTH,
 122                             NULL);
 123    }
 124
 125    AIO_WAIT_WHILE_UNLOCKED(NULL, server->connections > 0);
 126
 127    if (server->tlscreds) {
 128        object_unref(OBJECT(server->tlscreds));
 129    }
 130    g_free(server->tlsauthz);
 131
 132    g_free(server);
 133}
 134
 135static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
 136{
 137    Object *obj;
 138    QCryptoTLSCreds *creds;
 139
 140    obj = object_resolve_path_component(
 141        object_get_objects_root(), id);
 142    if (!obj) {
 143        error_setg(errp, "No TLS credentials with id '%s'",
 144                   id);
 145        return NULL;
 146    }
 147    creds = (QCryptoTLSCreds *)
 148        object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS);
 149    if (!creds) {
 150        error_setg(errp, "Object with id '%s' is not TLS credentials",
 151                   id);
 152        return NULL;
 153    }
 154
 155    if (!qcrypto_tls_creds_check_endpoint(creds,
 156                                          QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
 157                                          errp)) {
 158        return NULL;
 159    }
 160    object_ref(obj);
 161    return creds;
 162}
 163
 164
 165void nbd_server_start(SocketAddress *addr, uint32_t handshake_max_secs,
 166                      const char *tls_creds, const char *tls_authz,
 167                      uint32_t max_connections, Error **errp)
 168{
 169    if (nbd_server) {
 170        error_setg(errp, "NBD server already running");
 171        return;
 172    }
 173
 174    nbd_server = g_new0(NBDServerData, 1);
 175    nbd_server->max_connections = max_connections;
 176    nbd_server->handshake_max_secs = handshake_max_secs;
 177    nbd_server->listener = qio_net_listener_new();
 178
 179    qio_net_listener_set_name(nbd_server->listener,
 180                              "nbd-listener");
 181
 182    /*
 183     * Because this server is persistent, a backlog of SOMAXCONN is
 184     * better than trying to size it to max_connections.
 185     */
 186    if (qio_net_listener_open_sync(nbd_server->listener, addr, SOMAXCONN,
 187                                   errp) < 0) {
 188        goto error;
 189    }
 190
 191    if (tls_creds) {
 192        nbd_server->tlscreds = nbd_get_tls_creds(tls_creds, errp);
 193        if (!nbd_server->tlscreds) {
 194            goto error;
 195        }
 196    }
 197
 198    nbd_server->tlsauthz = g_strdup(tls_authz);
 199
 200    nbd_update_server_watch(nbd_server);
 201
 202    return;
 203
 204 error:
 205    nbd_server_free(nbd_server);
 206    nbd_server = NULL;
 207}
 208
 209void nbd_server_start_options(NbdServerOptions *arg, Error **errp)
 210{
 211    if (!arg->has_max_connections) {
 212        arg->max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
 213    }
 214    if (!arg->has_handshake_max_seconds) {
 215        arg->handshake_max_seconds = NBD_DEFAULT_HANDSHAKE_MAX_SECS;
 216    }
 217
 218    nbd_server_start(arg->addr, arg->handshake_max_seconds, arg->tls_creds,
 219                     arg->tls_authz, arg->max_connections, errp);
 220}
 221
 222void qmp_nbd_server_start(bool has_handshake_max_secs,
 223                          uint32_t handshake_max_secs,
 224                          const char *tls_creds,
 225                          const char *tls_authz,
 226                          bool has_max_connections, uint32_t max_connections,
 227                          SocketAddressLegacy *addr,
 228                          Error **errp)
 229{
 230    SocketAddress *addr_flat = socket_address_flatten(addr);
 231
 232    if (!has_max_connections) {
 233        max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
 234    }
 235    if (!has_handshake_max_secs) {
 236        handshake_max_secs = NBD_DEFAULT_HANDSHAKE_MAX_SECS;
 237    }
 238
 239    nbd_server_start(addr_flat, handshake_max_secs, tls_creds, tls_authz,
 240                     max_connections, errp);
 241    qapi_free_SocketAddress(addr_flat);
 242}
 243
 244void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
 245{
 246    BlockExport *export;
 247    BlockDriverState *bs;
 248    BlockBackend *on_eject_blk;
 249    BlockExportOptions *export_opts;
 250
 251    bs = bdrv_lookup_bs(arg->device, arg->device, errp);
 252    if (!bs) {
 253        return;
 254    }
 255
 256    /*
 257     * block-export-add would default to the node-name, but we may have to use
 258     * the device name as a default here for compatibility.
 259     */
 260    if (!arg->name) {
 261        arg->name = g_strdup(arg->device);
 262    }
 263
 264    export_opts = g_new(BlockExportOptions, 1);
 265    *export_opts = (BlockExportOptions) {
 266        .type                   = BLOCK_EXPORT_TYPE_NBD,
 267        .id                     = g_strdup(arg->name),
 268        .node_name              = g_strdup(bdrv_get_node_name(bs)),
 269        .has_writable           = arg->has_writable,
 270        .writable               = arg->writable,
 271    };
 272    QAPI_CLONE_MEMBERS(BlockExportOptionsNbdBase, &export_opts->u.nbd,
 273                       qapi_NbdServerAddOptions_base(arg));
 274    if (arg->bitmap) {
 275        BlockDirtyBitmapOrStr *el = g_new(BlockDirtyBitmapOrStr, 1);
 276
 277        *el = (BlockDirtyBitmapOrStr) {
 278            .type = QTYPE_QSTRING,
 279            .u.local = g_strdup(arg->bitmap),
 280        };
 281        export_opts->u.nbd.has_bitmaps = true;
 282        QAPI_LIST_PREPEND(export_opts->u.nbd.bitmaps, el);
 283    }
 284
 285    /*
 286     * nbd-server-add doesn't complain when a read-only device should be
 287     * exported as writable, but simply downgrades it. This is an error with
 288     * block-export-add.
 289     */
 290    if (bdrv_is_read_only(bs)) {
 291        export_opts->has_writable = true;
 292        export_opts->writable = false;
 293    }
 294
 295    export = blk_exp_add(export_opts, errp);
 296    if (!export) {
 297        goto fail;
 298    }
 299
 300    /*
 301     * nbd-server-add removes the export when the named BlockBackend used for
 302     * @device goes away.
 303     */
 304    on_eject_blk = blk_by_name(arg->device);
 305    if (on_eject_blk) {
 306        nbd_export_set_on_eject_blk(export, on_eject_blk);
 307    }
 308
 309fail:
 310    qapi_free_BlockExportOptions(export_opts);
 311}
 312
 313void qmp_nbd_server_remove(const char *name,
 314                           bool has_mode, BlockExportRemoveMode mode,
 315                           Error **errp)
 316{
 317    BlockExport *exp;
 318
 319    exp = blk_exp_find(name);
 320    if (exp && exp->drv->type != BLOCK_EXPORT_TYPE_NBD) {
 321        error_setg(errp, "Block export '%s' is not an NBD export", name);
 322        return;
 323    }
 324
 325    qmp_block_export_del(name, has_mode, mode, errp);
 326}
 327
 328void qmp_nbd_server_stop(Error **errp)
 329{
 330    if (!nbd_server) {
 331        error_setg(errp, "NBD server not running");
 332        return;
 333    }
 334
 335    blk_exp_close_all_type(BLOCK_EXPORT_TYPE_NBD);
 336
 337    nbd_server_free(nbd_server);
 338    nbd_server = NULL;
 339}
 340