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