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 nbd_server_start(SocketAddress *addr, const char *tls_creds, 103 Error **errp) 104 { 105 if (nbd_server) { 106 error_setg(errp, "NBD server already running"); 107 return; 108 } 109 110 nbd_server = g_new0(NBDServerData, 1); 111 nbd_server->watch = -1; 112 nbd_server->listen_ioc = qio_channel_socket_new(); 113 qio_channel_set_name(QIO_CHANNEL(nbd_server->listen_ioc), 114 "nbd-listener"); 115 if (qio_channel_socket_listen_sync( 116 nbd_server->listen_ioc, addr, errp) < 0) { 117 goto error; 118 } 119 120 if (tls_creds) { 121 nbd_server->tlscreds = nbd_get_tls_creds(tls_creds, errp); 122 if (!nbd_server->tlscreds) { 123 goto error; 124 } 125 126 /* TODO SOCKET_ADDRESS_TYPE_FD where fd has AF_INET or AF_INET6 */ 127 if (addr->type != SOCKET_ADDRESS_TYPE_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_start(SocketAddressLegacy *addr, 148 bool has_tls_creds, const char *tls_creds, 149 Error **errp) 150 { 151 SocketAddress *addr_flat = socket_address_flatten(addr); 152 153 nbd_server_start(addr_flat, tls_creds, errp); 154 qapi_free_SocketAddress(addr_flat); 155 } 156 157 void qmp_nbd_server_add(const char *device, bool has_writable, bool writable, 158 Error **errp) 159 { 160 BlockDriverState *bs = NULL; 161 BlockBackend *on_eject_blk; 162 NBDExport *exp; 163 164 if (!nbd_server) { 165 error_setg(errp, "NBD server not running"); 166 return; 167 } 168 169 if (nbd_export_find(device)) { 170 error_setg(errp, "NBD server already exporting device '%s'", device); 171 return; 172 } 173 174 on_eject_blk = blk_by_name(device); 175 176 bs = bdrv_lookup_bs(device, device, errp); 177 if (!bs) { 178 return; 179 } 180 181 if (!has_writable) { 182 writable = false; 183 } 184 if (bdrv_is_read_only(bs)) { 185 writable = false; 186 } 187 188 exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY, 189 NULL, false, on_eject_blk, errp); 190 if (!exp) { 191 return; 192 } 193 194 nbd_export_set_name(exp, device); 195 196 /* The list of named exports has a strong reference to this export now and 197 * our only way of accessing it is through nbd_export_find(), so we can drop 198 * the strong reference that is @exp. */ 199 nbd_export_put(exp); 200 } 201 202 void qmp_nbd_server_stop(Error **errp) 203 { 204 nbd_export_close_all(); 205 206 nbd_server_free(nbd_server); 207 nbd_server = NULL; 208 } 209