2013-12-01 22:23:41 +01:00
|
|
|
#ifndef NBD_CLIENT_H
|
|
|
|
#define NBD_CLIENT_H
|
|
|
|
|
|
|
|
#include "qemu-common.h"
|
|
|
|
#include "block/nbd.h"
|
|
|
|
#include "block/block_int.h"
|
2016-02-10 19:41:01 +01:00
|
|
|
#include "io/channel-socket.h"
|
2013-12-01 22:23:41 +01:00
|
|
|
|
|
|
|
/* #define DEBUG_NBD */
|
|
|
|
|
|
|
|
#if defined(DEBUG_NBD)
|
|
|
|
#define logout(fmt, ...) \
|
|
|
|
fprintf(stderr, "nbd\t%-24s" fmt, __func__, ##__VA_ARGS__)
|
|
|
|
#else
|
|
|
|
#define logout(fmt, ...) ((void)0)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define MAX_NBD_REQUESTS 16
|
|
|
|
|
2017-08-22 14:51:13 +02:00
|
|
|
typedef struct {
|
|
|
|
Coroutine *coroutine;
|
2017-10-27 12:40:37 +02:00
|
|
|
uint64_t offset; /* original offset of the request */
|
2017-08-22 14:51:13 +02:00
|
|
|
bool receiving; /* waiting for read_reply_co? */
|
|
|
|
} NBDClientRequest;
|
|
|
|
|
2016-10-14 20:33:06 +02:00
|
|
|
typedef struct NBDClientSession {
|
2016-02-10 19:41:01 +01:00
|
|
|
QIOChannelSocket *sioc; /* The master data channel */
|
|
|
|
QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
|
2017-07-07 22:30:41 +02:00
|
|
|
NBDExportInfo info;
|
2013-12-01 22:23:41 +01:00
|
|
|
|
|
|
|
CoMutex send_mutex;
|
nbd: Use CoQueue for free_sema instead of CoMutex
NBD is using the CoMutex in a way that wasn't anticipated. For example, if there are
N(N=26, MAX_NBD_REQUESTS=16) nbd write requests, so we will invoke nbd_client_co_pwritev
N times.
----------------------------------------------------------------------------------------
time request Actions
1 1 in_flight=1, Coroutine=C1
2 2 in_flight=2, Coroutine=C2
...
15 15 in_flight=15, Coroutine=C15
16 16 in_flight=16, Coroutine=C16, free_sema->holder=C16, mutex->locked=true
17 17 in_flight=16, Coroutine=C17, queue C17 into free_sema->queue
18 18 in_flight=16, Coroutine=C18, queue C18 into free_sema->queue
...
26 N in_flight=16, Coroutine=C26, queue C26 into free_sema->queue
----------------------------------------------------------------------------------------
Once nbd client recieves request No.16' reply, we will re-enter C16. It's ok, because
it's equal to 'free_sema->holder'.
----------------------------------------------------------------------------------------
time request Actions
27 16 in_flight=15, Coroutine=C16, free_sema->holder=C16, mutex->locked=false
----------------------------------------------------------------------------------------
Then nbd_coroutine_end invokes qemu_co_mutex_unlock what will pop coroutines from
free_sema->queue's head and enter C17. More free_sema->holder is C17 now.
----------------------------------------------------------------------------------------
time request Actions
28 17 in_flight=16, Coroutine=C17, free_sema->holder=C17, mutex->locked=true
----------------------------------------------------------------------------------------
In above scenario, we only recieves request No.16' reply. As time goes by, nbd client will
almostly recieves replies from requests 1 to 15 rather than request 17 who owns C17. In this
case, we will encounter assert "mutex->holder == self" failed since Kevin's commit 0e438cdc
"coroutine: Let CoMutex remember who holds it". For example, if nbd client recieves request
No.15' reply, qemu will stop unexpectedly:
----------------------------------------------------------------------------------------
time request Actions
29 15(most case) in_flight=15, Coroutine=C15, free_sema->holder=C17, mutex->locked=false
----------------------------------------------------------------------------------------
Per Paolo's suggestion "The simplest fix is to change it to CoQueue, which is like a condition
variable", this patch replaces CoMutex with CoQueue.
Cc: Wen Congyang <wency@cn.fujitsu.com>
Reported-by: zhanghailiang <zhang.zhanghailiang@huawei.com>
Suggested-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Changlong Xie <xiecl.fnst@cn.fujitsu.com>
Message-Id: <1476267508-19499-1-git-send-email-xiecl.fnst@cn.fujitsu.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2016-10-12 12:18:28 +02:00
|
|
|
CoQueue free_sema;
|
2017-02-13 14:52:24 +01:00
|
|
|
Coroutine *read_reply_co;
|
2013-12-01 22:23:41 +01:00
|
|
|
int in_flight;
|
|
|
|
|
2017-08-22 14:51:13 +02:00
|
|
|
NBDClientRequest requests[MAX_NBD_REQUESTS];
|
2016-10-14 20:33:07 +02:00
|
|
|
NBDReply reply;
|
nbd-client: Fix regression when server sends garbage
When we switched NBD to use coroutines for qemu 2.9 (in particular,
commit a12a712a), we introduced a regression: if a server sends us
garbage (such as a corrupted magic number), we quit the read loop
but do not stop sending further queued commands, resulting in the
client hanging when it never reads the response to those additional
commands. In qemu 2.8, we properly detected that the server is no
longer reliable, and cancelled all existing pending commands with
EIO, then tore down the socket so that all further command attempts
get EPIPE.
Restore the proper behavior of quitting (almost) all communication
with a broken server: Once we know we are out of sync or otherwise
can't trust the server, we must assume that any further incoming
data is unreliable and therefore end all pending commands with EIO,
and quit trying to send any further commands. As an exception, we
still (try to) send NBD_CMD_DISC to let the server know we are going
away (in part, because it is easier to do that than to further
refactor nbd_teardown_connection, and in part because it is the
only command where we do not have to wait for a reply).
Based on a patch by Vladimir Sementsov-Ogievskiy.
A malicious server can be created with the following hack,
followed by setting NBD_SERVER_DEBUG to a non-zero value in the
environment when running qemu-nbd:
| --- a/nbd/server.c
| +++ b/nbd/server.c
| @@ -919,6 +919,17 @@ static int nbd_send_reply(QIOChannel *ioc, NBDReply *reply, Error **errp)
| stl_be_p(buf + 4, reply->error);
| stq_be_p(buf + 8, reply->handle);
|
| + static int debug;
| + static int count;
| + if (!count++) {
| + const char *str = getenv("NBD_SERVER_DEBUG");
| + if (str) {
| + debug = atoi(str);
| + }
| + }
| + if (debug && !(count % debug)) {
| + buf[0] = 0;
| + }
| return nbd_write(ioc, buf, sizeof(buf), errp);
| }
Reported-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <20170814213426.24681-1-eblake@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
2017-08-14 23:34:26 +02:00
|
|
|
bool quit;
|
2016-10-14 20:33:06 +02:00
|
|
|
} NBDClientSession;
|
2013-12-01 22:23:41 +01:00
|
|
|
|
2016-10-14 20:33:06 +02:00
|
|
|
NBDClientSession *nbd_get_client_session(BlockDriverState *bs);
|
2015-02-06 22:06:16 +01:00
|
|
|
|
2016-02-10 19:41:01 +01:00
|
|
|
int nbd_client_init(BlockDriverState *bs,
|
|
|
|
QIOChannelSocket *sock,
|
|
|
|
const char *export_name,
|
2016-02-10 19:41:12 +01:00
|
|
|
QCryptoTLSCreds *tlscreds,
|
|
|
|
const char *hostname,
|
2015-02-06 22:06:16 +01:00
|
|
|
Error **errp);
|
|
|
|
void nbd_client_close(BlockDriverState *bs);
|
|
|
|
|
2017-06-09 12:18:08 +02:00
|
|
|
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes);
|
2015-02-06 22:06:16 +01:00
|
|
|
int nbd_client_co_flush(BlockDriverState *bs);
|
2016-07-16 01:23:07 +02:00
|
|
|
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
|
|
|
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
2016-10-14 20:33:18 +02:00
|
|
|
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
2017-06-09 12:18:08 +02:00
|
|
|
int bytes, BdrvRequestFlags flags);
|
2016-07-16 01:23:07 +02:00
|
|
|
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
|
|
|
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
2015-02-06 22:06:16 +01:00
|
|
|
|
|
|
|
void nbd_client_detach_aio_context(BlockDriverState *bs);
|
|
|
|
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
|
|
|
AioContext *new_context);
|
2014-05-08 16:34:43 +02:00
|
|
|
|
2013-12-01 22:23:41 +01:00
|
|
|
#endif /* NBD_CLIENT_H */
|