diff options
Diffstat (limited to 'nbd')
-rw-r--r-- | nbd/client-connection.c | 5 | ||||
-rw-r--r-- | nbd/common.c | 26 | ||||
-rw-r--r-- | nbd/nbd-internal.h | 7 | ||||
-rw-r--r-- | nbd/server.c | 93 | ||||
-rw-r--r-- | nbd/trace-events | 1 |
5 files changed, 117 insertions, 15 deletions
diff --git a/nbd/client-connection.c b/nbd/client-connection.c index f9da67c..79ea97e 100644 --- a/nbd/client-connection.c +++ b/nbd/client-connection.c @@ -31,6 +31,8 @@ #include "qapi/clone-visitor.h" #include "qemu/coroutine.h" +#include "nbd/nbd-internal.h" + struct NBDClientConnection { /* Initialization constants, never change */ SocketAddress *saddr; /* address to connect to */ @@ -140,6 +142,7 @@ static int nbd_connect(QIOChannelSocket *sioc, SocketAddress *addr, return ret; } + nbd_set_socket_send_buffer(sioc); qio_channel_set_delay(QIO_CHANNEL(sioc), false); if (!info) { @@ -410,7 +413,7 @@ nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info, */ void nbd_co_establish_connection_cancel(NBDClientConnection *conn) { - Coroutine *wait_co; + Coroutine *wait_co = NULL; WITH_QEMU_LOCK_GUARD(&conn->mutex) { wait_co = g_steal_pointer(&conn->wait_co); diff --git a/nbd/common.c b/nbd/common.c index 589a748..2a133a6 100644 --- a/nbd/common.c +++ b/nbd/common.c @@ -18,6 +18,9 @@ #include "qemu/osdep.h" #include "trace.h" +#include "io/channel-socket.h" +#include "qapi/error.h" +#include "qemu/units.h" #include "nbd-internal.h" /* Discard length bytes from channel. Return -errno on failure and 0 on @@ -264,3 +267,26 @@ const char *nbd_mode_lookup(NBDMode mode) return "<unknown>"; } } + +/* + * Testing shows that 2m send buffer is optimal. Changing the receive buffer + * size has no effect on performance. + * On Linux we need to increase net.core.wmem_max to make this effective. + */ +#if defined(__APPLE__) || defined(__linux__) +#define UNIX_STREAM_SOCKET_SEND_BUFFER_SIZE (2 * MiB) +#endif + +void nbd_set_socket_send_buffer(QIOChannelSocket *sioc) +{ +#ifdef UNIX_STREAM_SOCKET_SEND_BUFFER_SIZE + if (sioc->localAddr.ss_family == AF_UNIX) { + size_t size = UNIX_STREAM_SOCKET_SEND_BUFFER_SIZE; + Error *errp = NULL; + + if (qio_channel_socket_set_send_buffer(sioc, size, &errp) < 0) { + warn_report_err(errp); + } + } +#endif /* UNIX_STREAM_SOCKET_SEND_BUFFER_SIZE */ +} diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h index 9189510..6bafeef 100644 --- a/nbd/nbd-internal.h +++ b/nbd/nbd-internal.h @@ -10,7 +10,7 @@ #ifndef NBD_INTERNAL_H #define NBD_INTERNAL_H #include "block/nbd.h" -#include "sysemu/block-backend.h" +#include "system/block-backend.h" #include "io/channel-tls.h" #include "qemu/iov.h" @@ -74,4 +74,9 @@ static inline int nbd_write(QIOChannel *ioc, const void *buffer, size_t size, int nbd_drop(QIOChannel *ioc, size_t size, Error **errp); +/* nbd_set_socket_send_buffer + * Set the socket send buffer size for optimal performance. + */ +void nbd_set_socket_send_buffer(QIOChannelSocket *sioc); + #endif diff --git a/nbd/server.c b/nbd/server.c index 892797b..d242be9 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -124,12 +124,14 @@ struct NBDMetaContexts { struct NBDClient { int refcount; /* atomic */ void (*close_fn)(NBDClient *client, bool negotiated); + void *owner; QemuMutex lock; NBDExport *exp; QCryptoTLSCreds *tlscreds; char *tlsauthz; + uint32_t handshake_max_secs; QIOChannelSocket *sioc; /* The underlying data channel */ QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */ @@ -1148,8 +1150,8 @@ nbd_negotiate_meta_queries(NBDClient *client, Error **errp) * Return: * -errno on error, errp is set * 0 on successful negotiation, errp is not set - * 1 if client sent NBD_OPT_ABORT, i.e. on valid disconnect, - * errp is not set + * 1 if client sent NBD_OPT_ABORT (i.e. on valid disconnect) or never + * wrote anything (i.e. port probe); errp is not set */ static coroutine_fn int nbd_negotiate_options(NBDClient *client, Error **errp) @@ -1173,8 +1175,13 @@ nbd_negotiate_options(NBDClient *client, Error **errp) ... Rest of request */ - if (nbd_read32(client->ioc, &flags, "flags", errp) < 0) { - return -EIO; + /* + * Intentionally ignore errors on this first read - we do not want + * to be noisy about a mere port probe, but only for clients that + * start talking the protocol and then quit abruptly. + */ + if (nbd_read32(client->ioc, &flags, "flags", NULL) < 0) { + return 1; } client->mode = NBD_MODE_EXPORT_NAME; trace_nbd_negotiate_options_flags(flags); @@ -1381,8 +1388,8 @@ nbd_negotiate_options(NBDClient *client, Error **errp) * Return: * -errno on error, errp is set * 0 on successful negotiation, errp is not set - * 1 if client sent NBD_OPT_ABORT, i.e. on valid disconnect, - * errp is not set + * 1 if client sent NBD_OPT_ABORT (i.e. on valid disconnect) or never + * wrote anything (i.e. port probe); errp is not set */ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) { @@ -1413,9 +1420,12 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) stq_be_p(buf + 8, NBD_OPTS_MAGIC); stw_be_p(buf + 16, NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES); - if (nbd_write(client->ioc, buf, 18, errp) < 0) { - error_prepend(errp, "write failed: "); - return -EINVAL; + /* + * Be silent about failure to write our greeting: there is nothing + * wrong with a client testing if our port is alive. + */ + if (nbd_write(client->ioc, buf, 18, NULL) < 0) { + return 1; } ret = nbd_negotiate_options(client, errp); if (ret != 0) { @@ -1972,7 +1982,7 @@ static void nbd_export_request_shutdown(BlockExport *blk_exp) blk_exp_ref(&exp->common); /* - * TODO: Should we expand QMP NbdServerRemoveNode enum to allow a + * TODO: Should we expand QMP BlockExportRemoveMode enum to allow a * close mode that stops advertising the export to new clients but * still permits existing clients to run to completion? Because of * that possibility, nbd_export_close() can be called more than @@ -2016,6 +2026,7 @@ static void nbd_export_delete(BlockExport *blk_exp) const BlockExportDriver blk_exp_nbd = { .type = BLOCK_EXPORT_TYPE_NBD, .instance_size = sizeof(NBDExport), + .supports_inactive = true, .create = nbd_export_create, .delete = nbd_export_delete, .request_shutdown = nbd_export_request_shutdown, @@ -2910,6 +2921,22 @@ static coroutine_fn int nbd_handle_request(NBDClient *client, NBDExport *exp = client->exp; char *msg; size_t i; + bool inactive; + + WITH_GRAPH_RDLOCK_GUARD() { + inactive = bdrv_is_inactive(blk_bs(exp->common.blk)); + if (inactive) { + switch (request->type) { + case NBD_CMD_READ: + /* These commands are allowed on inactive nodes */ + break; + default: + /* Return an error for the rest */ + return nbd_send_generic_reply(client, request, -EPERM, + "export is inactive", errp); + } + } + } switch (request->type) { case NBD_CMD_CACHE: @@ -3184,35 +3211,65 @@ static void nbd_client_receive_next_request(NBDClient *client) } } +static void nbd_handshake_timer_cb(void *opaque) +{ + QIOChannel *ioc = opaque; + + trace_nbd_handshake_timer_cb(); + qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL); +} + static coroutine_fn void nbd_co_client_start(void *opaque) { NBDClient *client = opaque; Error *local_err = NULL; + QEMUTimer *handshake_timer = NULL; qemu_co_mutex_init(&client->send_lock); + /* + * Create a timer to bound the time spent in negotiation. If the + * timer expires, it is likely nbd_negotiate will fail because the + * socket was shutdown. + */ + if (client->handshake_max_secs > 0) { + handshake_timer = aio_timer_new(qemu_get_aio_context(), + QEMU_CLOCK_REALTIME, + SCALE_NS, + nbd_handshake_timer_cb, + client->sioc); + timer_mod(handshake_timer, + qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + + client->handshake_max_secs * NANOSECONDS_PER_SECOND); + } + if (nbd_negotiate(client, &local_err)) { if (local_err) { error_report_err(local_err); } + timer_free(handshake_timer); client_close(client, false); return; } + timer_free(handshake_timer); WITH_QEMU_LOCK_GUARD(&client->lock) { nbd_client_receive_next_request(client); } } /* - * Create a new client listener using the given channel @sioc. + * Create a new client listener using the given channel @sioc and @owner. * Begin servicing it in a coroutine. When the connection closes, call - * @close_fn with an indication of whether the client completed negotiation. + * @close_fn with an indication of whether the client completed negotiation + * within @handshake_max_secs seconds (0 for unbounded). */ void nbd_client_new(QIOChannelSocket *sioc, + uint32_t handshake_max_secs, QCryptoTLSCreds *tlscreds, const char *tlsauthz, - void (*close_fn)(NBDClient *, bool)) + void (*close_fn)(NBDClient *, bool), + void *owner) { NBDClient *client; Coroutine *co; @@ -3225,13 +3282,23 @@ void nbd_client_new(QIOChannelSocket *sioc, object_ref(OBJECT(client->tlscreds)); } client->tlsauthz = g_strdup(tlsauthz); + client->handshake_max_secs = handshake_max_secs; client->sioc = sioc; qio_channel_set_delay(QIO_CHANNEL(sioc), false); object_ref(OBJECT(client->sioc)); client->ioc = QIO_CHANNEL(sioc); object_ref(OBJECT(client->ioc)); client->close_fn = close_fn; + client->owner = owner; + + nbd_set_socket_send_buffer(sioc); co = qemu_coroutine_create(nbd_co_client_start, client); qemu_coroutine_enter(co); } + +void * +nbd_client_owner(NBDClient *client) +{ + return client->owner; +} diff --git a/nbd/trace-events b/nbd/trace-events index 00ae321..cbd0a4a 100644 --- a/nbd/trace-events +++ b/nbd/trace-events @@ -76,6 +76,7 @@ nbd_co_receive_request_payload_received(uint64_t cookie, uint64_t len) "Payload nbd_co_receive_ext_payload_compliance(uint64_t from, uint64_t len) "client sent non-compliant write without payload flag: from=0x%" PRIx64 ", len=0x%" PRIx64 nbd_co_receive_align_compliance(const char *op, uint64_t from, uint64_t len, uint32_t align) "client sent non-compliant unaligned %s request: from=0x%" PRIx64 ", len=0x%" PRIx64 ", align=0x%" PRIx32 nbd_trip(void) "Reading request" +nbd_handshake_timer_cb(void) "client took too long to negotiate" # client-connection.c nbd_connect_thread_sleep(uint64_t timeout) "timeout %" PRIu64 |