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 "sysemu/blockdev.h" 14 #include "sysemu/block-backend.h" 15 #include "hw/block/block.h" 16 #include "qapi/qmp/qerror.h" 17 #include "sysemu/sysemu.h" 18 #include "qmp-commands.h" 19 #include "block/nbd.h" 20 #include "io/channel-socket.h" 21 22 typedef struct NBDServerData { 23 QIOChannelSocket *listen_ioc; 24 int watch; 25 QCryptoTLSCreds *tlscreds; 26 } NBDServerData; 27 28 static NBDServerData *nbd_server; 29 30 31 static gboolean nbd_accept(QIOChannel *ioc, GIOCondition condition, 32 gpointer opaque) 33 { 34 QIOChannelSocket *cioc; 35 36 if (!nbd_server) { 37 return FALSE; 38 } 39 40 cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), 41 NULL); 42 if (!cioc) { 43 return TRUE; 44 } 45 46 qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server"); 47 nbd_client_new(NULL, cioc, 48 nbd_server->tlscreds, NULL, 49 nbd_client_put); 50 object_unref(OBJECT(cioc)); 51 return TRUE; 52 } 53 54 55 static void nbd_server_free(NBDServerData *server) 56 { 57 if (!server) { 58 return; 59 } 60 61 if (server->watch != -1) { 62 g_source_remove(server->watch); 63 } 64 object_unref(OBJECT(server->listen_ioc)); 65 if (server->tlscreds) { 66 object_unref(OBJECT(server->tlscreds)); 67 } 68 69 g_free(server); 70 } 71 72 static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp) 73 { 74 Object *obj; 75 QCryptoTLSCreds *creds; 76 77 obj = object_resolve_path_component( 78 object_get_objects_root(), id); 79 if (!obj) { 80 error_setg(errp, "No TLS credentials with id '%s'", 81 id); 82 return NULL; 83 } 84 creds = (QCryptoTLSCreds *) 85 object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS); 86 if (!creds) { 87 error_setg(errp, "Object with id '%s' is not TLS credentials", 88 id); 89 return NULL; 90 } 91 92 if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { 93 error_setg(errp, 94 "Expecting TLS credentials with a server endpoint"); 95 return NULL; 96 } 97 object_ref(obj); 98 return creds; 99 } 100 101 102 void qmp_nbd_server_start(SocketAddress *addr, 103 bool has_tls_creds, const char *tls_creds, 104 Error **errp) 105 { 106 if (nbd_server) { 107 error_setg(errp, "NBD server already running"); 108 return; 109 } 110 111 nbd_server = g_new0(NBDServerData, 1); 112 nbd_server->watch = -1; 113 nbd_server->listen_ioc = qio_channel_socket_new(); 114 qio_channel_set_name(QIO_CHANNEL(nbd_server->listen_ioc), 115 "nbd-listener"); 116 if (qio_channel_socket_listen_sync( 117 nbd_server->listen_ioc, addr, errp) < 0) { 118 goto error; 119 } 120 121 if (has_tls_creds) { 122 nbd_server->tlscreds = nbd_get_tls_creds(tls_creds, errp); 123 if (!nbd_server->tlscreds) { 124 goto error; 125 } 126 127 if (addr->type != SOCKET_ADDRESS_KIND_INET) { 128 error_setg(errp, "TLS is only supported with IPv4/IPv6"); 129 goto error; 130 } 131 } 132 133 nbd_server->watch = qio_channel_add_watch( 134 QIO_CHANNEL(nbd_server->listen_ioc), 135 G_IO_IN, 136 nbd_accept, 137 NULL, 138 NULL); 139 140 return; 141 142 error: 143 nbd_server_free(nbd_server); 144 nbd_server = NULL; 145 } 146 147 void qmp_nbd_server_add(const char *device, bool has_writable, bool writable, 148 Error **errp) 149 { 150 BlockDriverState *bs = NULL; 151 BlockBackend *on_eject_blk; 152 NBDExport *exp; 153 154 if (!nbd_server) { 155 error_setg(errp, "NBD server not running"); 156 return; 157 } 158 159 if (nbd_export_find(device)) { 160 error_setg(errp, "NBD server already exporting device '%s'", device); 161 return; 162 } 163 164 on_eject_blk = blk_by_name(device); 165 166 bs = bdrv_lookup_bs(device, device, errp); 167 if (!bs) { 168 return; 169 } 170 171 if (!has_writable) { 172 writable = false; 173 } 174 if (bdrv_is_read_only(bs)) { 175 writable = false; 176 } 177 178 exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY, 179 NULL, false, on_eject_blk, errp); 180 if (!exp) { 181 return; 182 } 183 184 nbd_export_set_name(exp, device); 185 186 /* The list of named exports has a strong reference to this export now and 187 * our only way of accessing it is through nbd_export_find(), so we can drop 188 * the strong reference that is @exp. */ 189 nbd_export_put(exp); 190 } 191 192 void qmp_nbd_server_stop(Error **errp) 193 { 194 nbd_export_close_all(); 195 196 nbd_server_free(nbd_server); 197 nbd_server = NULL; 198 } 199