On Monday, 18 December 2017 20:55:19 CET Jeff Cody wrote: > On Wed, Nov 15, 2017 at 05:26:48PM +0100, Pino Toscano wrote: > > Rewrite the implementation of the ssh block driver to use libssh instead > > of libssh2. The libssh library has various advantages over libssh2: > > - easier API for authentication (for example for using ssh-agent) > > - easier API for known_hosts handling > > - supports newer types of keys in known_hosts > > > > Kerberos authentication can be enabled once the libssh bug for it [1] is > > fixed. > > > > The development version of libssh (i.e. the future 0.8.x) supports > > fsync, so reuse the build time check for this. > > > > [1] https://red.libssh.org/issues/242 > > > > Signed-off-by: Pino Toscano <[email protected]> > > --- > > > > Changes from v2: > > - used again an own fd > > - fixed co_yield() implementation > > > > Changes from v1: > > - fixed jumbo packets writing > > - fixed missing 'err' assignment > > - fixed commit message > > > > block/Makefile.objs | 6 +- > > block/ssh.c | 494 > > ++++++++++++++++++++++++---------------------------- > > configure | 65 ++++--- > > 3 files changed, 259 insertions(+), 306 deletions(-) > > > > diff --git a/block/Makefile.objs b/block/Makefile.objs > > index 6eaf78a046..c0df21d0b4 100644 > > --- a/block/Makefile.objs > > +++ b/block/Makefile.objs > > @@ -20,7 +20,7 @@ block-obj-$(CONFIG_CURL) += curl.o > > block-obj-$(CONFIG_RBD) += rbd.o > > block-obj-$(CONFIG_GLUSTERFS) += gluster.o > > block-obj-$(CONFIG_VXHS) += vxhs.o > > -block-obj-$(CONFIG_LIBSSH2) += ssh.o > > +block-obj-$(CONFIG_LIBSSH) += ssh.o > > block-obj-y += accounting.o dirty-bitmap.o > > block-obj-y += write-threshold.o > > block-obj-y += backup.o > > @@ -41,8 +41,8 @@ rbd.o-libs := $(RBD_LIBS) > > gluster.o-cflags := $(GLUSTERFS_CFLAGS) > > gluster.o-libs := $(GLUSTERFS_LIBS) > > vxhs.o-libs := $(VXHS_LIBS) > > -ssh.o-cflags := $(LIBSSH2_CFLAGS) > > -ssh.o-libs := $(LIBSSH2_LIBS) > > +ssh.o-cflags := $(LIBSSH_CFLAGS) > > +ssh.o-libs := $(LIBSSH_LIBS) > > block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o > > dmg-bz2.o-libs := $(BZIP2_LIBS) > > qcow.o-libs := -lz > > diff --git a/block/ssh.c b/block/ssh.c > > index b049a16eb9..9e96c9d684 100644 > > --- a/block/ssh.c > > +++ b/block/ssh.c > > @@ -24,8 +24,8 @@ > > > > #include "qemu/osdep.h" > > > > -#include <libssh2.h> > > -#include <libssh2_sftp.h> > > +#include <libssh/libssh.h> > > +#include <libssh/sftp.h> > > > > #include "block/block_int.h" > > #include "qapi/error.h" > > @@ -41,14 +41,12 @@ > > /* DEBUG_SSH=1 enables the DPRINTF (debugging printf) statements in > > * this block driver code. > > * > > - * TRACE_LIBSSH2=<bitmask> enables tracing in libssh2 itself. Note > > - * that this requires that libssh2 was specially compiled with the > > - * `./configure --enable-debug' option, so most likely you will have > > - * to compile it yourself. The meaning of <bitmask> is described > > - * here: http://www.libssh2.org/libssh2_trace.html > > + * TRACE_LIBSSH=<level> enables tracing in libssh itself. > > + * The meaning of <level> is described here: > > + * http://api.libssh.org/master/group__libssh__log.html > > */ > > #define DEBUG_SSH 0 > > -#define TRACE_LIBSSH2 0 /* or try: LIBSSH2_TRACE_SFTP */ > > +#define TRACE_LIBSSH 0 /* see: SSH_LOG_* */ > > > > #define DPRINTF(fmt, ...) \ > > do { \ > > @@ -64,18 +62,14 @@ typedef struct BDRVSSHState { > > > > /* SSH connection. */ > > int sock; /* socket */ > > - LIBSSH2_SESSION *session; /* ssh session */ > > - LIBSSH2_SFTP *sftp; /* sftp session */ > > - LIBSSH2_SFTP_HANDLE *sftp_handle; /* sftp remote file handle */ > > + ssh_session session; /* ssh session */ > > + sftp_session sftp; /* sftp session */ > > + sftp_file sftp_handle; /* sftp remote file handle */ > > > > - /* See ssh_seek() function below. */ > > - int64_t offset; > > - bool offset_op_read; > > - > > - /* File attributes at open. We try to keep the .filesize field > > + /* File attributes at open. We try to keep the .size field > > * updated if it changes (eg by writing at the end of the file). > > */ > > - LIBSSH2_SFTP_ATTRIBUTES attrs; > > + sftp_attributes attrs; > > > > InetSocketAddress *inet; > > > > @@ -87,26 +81,23 @@ static void ssh_state_init(BDRVSSHState *s) > > { > > memset(s, 0, sizeof *s); > > s->sock = -1; > > - s->offset = -1; > > qemu_co_mutex_init(&s->lock); > > } > > > > static void ssh_state_free(BDRVSSHState *s) > > { > > + if (s->attrs) { > > + sftp_attributes_free(s->attrs); > > + } > > if (s->sftp_handle) { > > - libssh2_sftp_close(s->sftp_handle); > > + sftp_close(s->sftp_handle); > > } > > if (s->sftp) { > > - libssh2_sftp_shutdown(s->sftp); > > + sftp_free(s->sftp); > > } > > if (s->session) { > > - libssh2_session_disconnect(s->session, > > - "from qemu ssh client: " > > - "user closed the connection"); > > - libssh2_session_free(s->session); > > - } > > - if (s->sock >= 0) { > > - close(s->sock); > > Do we still want close(s->sock) here?
libssh takes ownership of the fd set using
ssh_options_set(..., SSH_OPTIONS_FD, ..)
so it is not needed in most of the cases; I will add a comment about
this, so it is not forgotten.
That said, that made me notice that in connect_to_ssh(), if there is
any error between inet_connect_saddr() and the aforementioned
ssh_options_set() then the socket is closed twice (once by ssh_free(),
and the second time in ssh_file_open())-- I reworked the socket
handling in connect_to_ssh(), so it is freed only when needed.
> > -
> > static coroutine_fn int ssh_read(BDRVSSHState *s, BlockDriverState *bs,
> > int64_t offset, size_t size,
> > QEMUIOVector *qiov)
> > @@ -975,7 +936,7 @@ static coroutine_fn int ssh_read(BDRVSSHState *s,
> > BlockDriverState *bs,
> >
> > DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
> >
> > - ssh_seek(s, offset, SSH_SEEK_READ);
> > + sftp_seek64(s->sftp_handle, offset);
> >
> > /* This keeps track of the current iovec element ('i'), where we
> > * will write to next ('buf'), and the end of the current iovec
> > @@ -985,35 +946,34 @@ static coroutine_fn int ssh_read(BDRVSSHState *s,
> > BlockDriverState *bs,
> > buf = i->iov_base;
> > end_of_vec = i->iov_base + i->iov_len;
> >
> > - /* libssh2 has a hard-coded limit of 2000 bytes per request,
> > - * although it will also do readahead behind our backs. Therefore
> > - * we may have to do repeated reads here until we have read 'size'
> > - * bytes.
> > - */
> > for (got = 0; got < size; ) {
> > again:
> > - DPRINTF("sftp_read buf=%p size=%zu", buf, end_of_vec - buf);
> > - r = libssh2_sftp_read(s->sftp_handle, buf, end_of_vec - buf);
> > + DPRINTF("sftp_read buf=%p size=%zu (actual size=%zu)",
> > + buf, end_of_vec - buf, MIN(end_of_vec - buf, 16384));
> > + /* The size of SFTP packets is limited to 32K bytes, so limit
> > + * the amount of data requested to 16K, as libssh currently
> > + * does not handle multiple requests on its own:
> > + * https://red.libssh.org/issues/58
> > + */
> > + r = sftp_read(s->sftp_handle, buf, MIN(end_of_vec - buf, 16384));
> > DPRINTF("sftp_read returned %zd", r);
> >
> > - if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> > + if (r == SSH_AGAIN) {
> > co_yield(s, bs);
> > goto again;
>
> Hmm, this does not work.
>
> If I test with an image create via ssh, it works fine. However, trying to
> read an image that was not create via ssh, it hangs and loops here.
>
> The only difference between the two images is the actual image size, and the
> zero padding:
>
> ./qemu-img create -f qcow2 ssh://127.0.0.1/tmp/test-1.qcow2 100M
> ./qemu-img create -f qcow2 /tmp/test-2.qcow2 100M
>
>
> Created image differences:
>
> diff <(xxd -c 4 -a /tmp/test-1.qcow2) <(xxd -c 4 -a /tmp/test-2.qcow2)
> 45c45
> < 000301fc: 0000 0000 ....
> ---
> > 00030004: 0000 0000 ....
>
>
> Now when trying to read the images with ssh, test-2 will hang forever (at
> sector 384):
>
> This hangs: ./qemu-io -c "read 0 100M" ssh://127.0.0.1/tmp/test-2.qcow2
> This works: ./qemu-io -c "read 0 100M" ssh://127.0.0.1/tmp/test-1.qcow2
>
> We seem to be missing EOF in the case of a short read.
Indeed, this is something I can reproduce it locally too, thanks.
> Looking into the libssh APIs some:
>
> The call to sftp_read() seems to be checking the return code incorrectly.
> From the libssh API documentation, sftp_read returns < 0 on error, or the
> number of bytes read. On error, sftp error is set, so this implies to me
> that you should be calling sftp_get_error() to obtain the actual error
> value.
>
> But this isn't the only issue... sftp_read() is returning 0 instead of a
> value < 0. I don't know if this is by design in libssh, or a libssh bug.
After a better look, it looks like an EOF reported by the server gives
as result sftp_read() = 0, and sftp_get_error() = SSH_FX_EOF.
> I am using libssh version 0.7.4-1.fc25. Looking at the libssh git repo,
> this commit seems suspicious, but it appears to be present in 0.7.4, so I'm
> not sure:
>
> commit 6697f85b5053e36a880f725ea87d1fbba5ee0563
> Author: Jeremy Cross <[email protected]>
> Date: Mon Jul 25 22:55:04 2016 +0000
>
> sftp: ensure sftp_packet_read recognizes channel EOF to avoid infinite
> loop
>
> Signed-off-by: Jeremy Cross <[email protected]>
> Reviewed-by: Andreas Schneider <[email protected]>
> (cherry picked from commit dbf72ffba2ad5b5694cd55aa1a7ca99053d20386)
>
> diff --git a/src/sftp.c b/src/sftp.c
> index 6bcd8a6..fa2d3f4 100644
> --- a/src/sftp.c
> +++ b/src/sftp.c
> @@ -334,7 +334,7 @@ sftp_packet sftp_packet_read(sftp_session sftp) {
> do {
> // read from channel until 4 bytes have been read or an error occurs
> s=ssh_channel_read(sftp->channel, buffer+r, 4-r, 0);
> - if (s < 0) {
> + if (s <= 0) {
> ssh_buffer_free(packet->payload);
> SAFE_FREE(packet);
> return NULL;
This was one of the fixes, but it had side effects (like spinning on
EOF), and it was fixed with 1b0bf852bef0acfde0825163a6d313a5654b1d74,
which is in 0.7.4 too.
> In any case, If I change the above hunk of your patch from this:
>
> > + r = sftp_read(s->sftp_handle, buf, MIN(end_of_vec - buf, 16384));
> > DPRINTF("sftp_read returned %zd", r);
> >
> > - if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> > + if (r == SSH_AGAIN) {
> > co_yield(s, bs);
> > goto again;
>
>
> To this:
>
>
> + r = sftp_read(s->sftp_handle, buf, MIN(end_of_vec - buf, 16384));
> DPRINTF("sftp_read returned %zd", r);
>
> +
> + if (r <= 0) {
> + r = sftp_get_error(s->sftp);
> + }
> - if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> + if (r == SSH_AGAIN) {
> co_yield(s, bs);
> goto again;
>
>
> Then I appear to hit SSH_EOF correctly.
>
> However, I am not sure it is always valid to check sftp_get_error() when 0
> is returned for sftp_read(), so I don't know if we can rely on this as a
> workaround.
This does not look a proper check though, since what sftp_get_error()
returns (i.e. the various SSH_FX_* constants) is different than
SSH_AGAIN & co. My take on this is to check for SSH_FX_EOF as sftp
error when sftp_read() returns 0.
>
> > }
> > - if (r < 0) {
> > - sftp_error_report(s, "read failed");
> > - s->offset = -1;
> > - return -EIO;
> > - }
> > - if (r == 0) {
> > + if (r == SSH_EOF) {
> > /* EOF: Short read so pad the buffer with zeroes and return
> > it. */
> > qemu_iovec_memset(qiov, got, 0, size - got);
> > return 0;
> > }
> > + if (r < 0) {
> > + sftp_error_report(s, "read failed");
> > + return -EIO;
> > + }
> >
> > got += r;
> > buf += r;
> > - s->offset += r;
> > if (buf >= end_of_vec && got < size) {
> > i++;
> > buf = i->iov_base;
> > @@ -1050,7 +1010,7 @@ static int ssh_write(BDRVSSHState *s,
> > BlockDriverState *bs,
> >
> > DPRINTF("offset=%" PRIi64 " size=%zu", offset, size);
> >
> > - ssh_seek(s, offset, SSH_SEEK_WRITE);
> > + sftp_seek64(s->sftp_handle, offset);
> >
> > /* This keeps track of the current iovec element ('i'), where we
> > * will read from next ('buf'), and the end of the current iovec
> > @@ -1062,45 +1022,35 @@ static int ssh_write(BDRVSSHState *s,
> > BlockDriverState *bs,
> >
> > for (written = 0; written < size; ) {
> > again:
> > - DPRINTF("sftp_write buf=%p size=%zu", buf, end_of_vec - buf);
> > - r = libssh2_sftp_write(s->sftp_handle, buf, end_of_vec - buf);
> > + DPRINTF("sftp_write buf=%p size=%zu (actual size=%zu)",
> > + buf, end_of_vec - buf, MIN(end_of_vec - buf, 131072));
> > + /* Avoid too large data packets, as libssh currently does not
> > + * handle multiple requests on its own:
> > + * https://red.libssh.org/issues/58
> > + */
> > + r = sftp_write(s->sftp_handle, buf, MIN(end_of_vec - buf, 131072));
> > DPRINTF("sftp_write returned %zd", r);
> >
> > - if (r == LIBSSH2_ERROR_EAGAIN || r == LIBSSH2_ERROR_TIMEOUT) {
> > + if (r == SSH_AGAIN) {
> > co_yield(s, bs);
> > goto again;
> > }
>
> I didn't verify, but I assume we probably have a similar issue here.
I think here there should not be the same issue.
--
Pino Toscano
signature.asc
Description: This is a digitally signed message part.
