[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-block] [PATCH v4] ssh: switch from libssh2 to libssh
From: |
Pino Toscano |
Subject: |
Re: [Qemu-block] [PATCH v4] ssh: switch from libssh2 to libssh |
Date: |
Mon, 25 Jun 2018 19:41:59 +0200 |
On Tuesday, 13 February 2018 19:49:12 CET Max Reitz wrote:
> On 2018-01-18 17:44, 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 buxg 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 <address@hidden>
> > ---
Sorry for the (very late) reply.
I fixed basically all the code issues noted with this review; follow
few replies/notes that are not just "fixed".
> > Changes from v3:
> > - fix socket cleanup in connect_to_ssh()
> > - add comments about the socket cleanup
> > - improve the error reporting (closer to what was with libssh2)
> > - improve EOF detection on sftp_read()
> >
> > 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
>
> One thing: The performance seems to have dropped hugely, from what I can
> tell.
>
> Before this patch, running the iotests 1-10 over ssh (raw/ssh) took
> 12.6 s. With this patch, they take 59.3 s. Perhaps the starkest
> contrast can be seen in test 1, which took 4 s before and 27 s after --
> this test simply reads and writes 128 MB of continuous data.
>
> I like having elliptic curves, but I think this patch needs optimization
> work before we can replace libssh2.
One thing that I discovered helping a lot is setting the TCP_NODELAY
option on the socket, to disable the Nagle algorithm; this drastically
reduced the overhead, which now does not seem to be more than 200% on
the very intensive tests (at least with my benchmarks).
Also using libssh from master shows more improvements too (and a bit
more of instability though, but that's a different story), and the
resulting overhead seems more acceptable to me now.
>
> > block/Makefile.objs | 6 +-
> > block/ssh.c | 522
> > ++++++++++++++++++++++++----------------------------
> > configure | 65 ++++---
> > 3 files changed, 278 insertions(+), 315 deletions(-)
>
> [...]
>
> > diff --git a/block/ssh.c b/block/ssh.c
> > index b049a16eb9..2975fc27d8 100644
> > --- a/block/ssh.c
> > +++ b/block/ssh.c
>
> [...]
>
> > @@ -87,27 +81,25 @@ 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);
> > + ssh_disconnect(s->session);
> > + ssh_free(s->session);
> > }
> > + /* s->sock is owned by the ssh_session, which free's it. */
>
> s/free's/frees/
>
> > }
> >
> > static void GCC_FMT_ATTR(3, 4)
> > @@ -121,13 +113,13 @@ session_error_setg(Error **errp, BDRVSSHState *s,
> > const char *fs, ...)
> > va_end(args);
> >
> > if (s->session) {
> > - char *ssh_err;
> > + const char *ssh_err;
> > int ssh_err_code;
> >
> > - /* This is not an errno. See <libssh2.h>. */
> > - ssh_err_code = libssh2_session_last_error(s->session,
> > - &ssh_err, NULL, 0);
> > - error_setg(errp, "%s: %s (libssh2 error code: %d)",
> > + /* This is not an errno. See <libssh/libssh.h>. */
> > + ssh_err = ssh_get_error(s->session);
> > + ssh_err_code = ssh_get_error_code(s->session);
> > + error_setg(errp, "%s: %s (libssh error code: %d)",
> > msg, ssh_err, ssh_err_code);
>
> Maybe we should not append the error info if there is no error.
>
> (Example:
>
> $ ./qemu-img info ssh://localhost/tmp/foo
> qemu-img: Could not open 'ssh://localhost/tmp/foo': no host key was
> found in known_hosts: (libssh error code: 0)
The current libssh2 code has the same issue -- the solution is what you
mention later on, i.e. use error_setg() directly.
There were few more "mismatches" on the usage of the error functions,
which I fixed.
> > @@ -291,68 +283,41 @@ static void ssh_parse_filename(const char *filename,
> > QDict *options,
> > static int check_host_key_knownhosts(BDRVSSHState *s,
> > const char *host, int port, Error
> > **errp)
> > {
> > - const char *home;
> > - char *knh_file = NULL;
> > - LIBSSH2_KNOWNHOSTS *knh = NULL;
> > - struct libssh2_knownhost *found;
> > - int ret, r;
> > - const char *hostkey;
> > - size_t len;
> > - int type;
> > + int ret;
> > + int state;
> >
> > - hostkey = libssh2_session_hostkey(s->session, &len, &type);
> > - if (!hostkey) {
> > - ret = -EINVAL;
> > - session_error_setg(errp, s, "failed to read remote host key");
> > - goto out;
> > - }
> > + state = ssh_is_server_known(s->session);
> >
> > - knh = libssh2_knownhost_init(s->session);
> > - if (!knh) {
> > - ret = -EINVAL;
> > - session_error_setg(errp, s,
> > - "failed to initialize known hosts support");
> > - goto out;
> > - }
> > -
> > - home = getenv("HOME");
> > - if (home) {
> > - knh_file = g_strdup_printf("%s/.ssh/known_hosts", home);
> > - } else {
> > - knh_file = g_strdup_printf("/root/.ssh/known_hosts");
> > - }
> > -
> > - /* Read all known hosts from OpenSSH-style known_hosts file. */
> > - libssh2_knownhost_readfile(knh, knh_file,
> > LIBSSH2_KNOWNHOST_FILE_OPENSSH);
> > -
> > - r = libssh2_knownhost_checkp(knh, host, port, hostkey, len,
> > - LIBSSH2_KNOWNHOST_TYPE_PLAIN|
> > - LIBSSH2_KNOWNHOST_KEYENC_RAW,
> > - &found);
> > - switch (r) {
> > - case LIBSSH2_KNOWNHOST_CHECK_MATCH:
> > + switch (state) {
> > + case SSH_SERVER_KNOWN_OK:
> > /* OK */
> > - DPRINTF("host key OK: %s", found->key);
> > break;
> > - case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
> > + case SSH_SERVER_KNOWN_CHANGED:
> > ret = -EINVAL;
> > session_error_setg(errp, s,
> > - "host key does not match the one in known_hosts"
> > - " (found key %s)", found->key);
> > + "host key does not match the one in known_hosts");
>
> Technically, this is against the specification which requires us to warn
> the user about a possible attack. :-P
Sadly libssh up to 0.7 does not provide an API for this; there is work
ongoing upstream to expand the "knownhosts" API, and query for this
information. Once it is finalized (e.g. with a 0.8 release), I will
make this block device use it.
> > @@ -407,18 +368,31 @@ static int compare_fingerprint(const unsigned char
> > *fingerprint, size_t len,
> >
> > static int
> > check_host_key_hash(BDRVSSHState *s, const char *hash,
> > - int hash_type, size_t fingerprint_len, Error **errp)
> > + enum ssh_publickey_hash_type type, size_t
> > fingerprint_len,
> > + Error **errp)
> > {
> > - const char *fingerprint;
> > + int r;
> > + ssh_key pubkey;
> > + unsigned char *server_hash;
> > + size_t server_hash_len;
> >
> > - fingerprint = libssh2_hostkey_hash(s->session, hash_type);
> > - if (!fingerprint) {
> > + r = ssh_get_publickey(s->session, &pubkey);
>
> The documentation says this is deprecated and one should use
> ssh_get_server_publickey() instead (it just looks like a rename, though).
Only in current master, i.e the future 0.8.
> > @@ -628,6 +570,8 @@ static int connect_to_ssh(BDRVSSHState *s, QDict
> > *options,
> > Error *local_err = NULL;
> > const char *user, *path, *host_key_check;
> > long port = 0;
> > + unsigned long portU = 0;
>
> I was about to say: How about making port an unsigned long and swapping
> the qemu_strtol() for a qemu_strtoul()?
>
> But I think you'd rather want an unsigned int instead (and that won't
> work with qemu_strtoul()).
After Dan implemented qemu_strtoui(), this now can use unsigned int.
> > ret = -EINVAL;
> > goto err;
> > }
> >
> > + /* Make sure the SFTP file is handled in blocking mode. */
> > + sftp_file_set_blocking(s->sftp_handle);
> > +
>
> Hm, but ssh_file_open() makes the SSH session non-blocking immediately
> afterwards, and it doesn't seem like the old libssh2 cared about how
> SFTP behaved. Was that the same thing in libssh2? Should
> ssh_file_open() make the SFTP session non-blocking as well?
The implementation for blocking/non-blocking follows the behaviour
currently implemented using libssh2, and in libssh2 itself.
--
Pino Toscano
signature.asc
Description: This is a digitally signed message part.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Re: [Qemu-block] [PATCH v4] ssh: switch from libssh2 to libssh,
Pino Toscano <=