[libvirt PATCH v3 08/10] remote: introduce virt-ssh-helper binary

Daniel Henrique Barboza danielhb413 at gmail.com
Mon Aug 24 16:23:39 UTC 2020



On 8/6/20 7:45 AM, Daniel P. Berrangé wrote:
> When accessing libvirtd over a SSH tunnel, the remote driver needs a way
> to proxy the SSH input/output stream to a suitable libvirt daemon. Tihs


s/Tihs/This


> is currently done by spawning netcat, pointing it to the libvirtd socket
> path. This is problematic for a number of reasons:
> 
>   - The socket path varies according to the --prefix chosen at build
>     time. The remote client is seeing the local prefix, but what we
>     need is the remote prefix
> 
>   - The socket path varies according to remote env variables, such as
>     the XDG_RUNTIME_DIR location. Again we see the local XDG_RUNTIME_DIR
>     value, but what we need is the remote value (if any)
> 
>   - The remote driver doesn't know whether it must connect to the legacy
>     libvirtd or the modular daemons, so must always assume legacy
>     libvirtd for back-compat. This means we'll always end up using the
>     virtproxyd daemon adding an extra hop in the RPC layer.
> 
>   - We can not able to autospawn the libvirtd daemon for session mode
>     access
> 
> To address these problems this patch introduces the 'virtd-ssh-helper'
> program which takes the URI for the remote driver as a CLI parameter.
> It then figures out which daemon to connect to and its socket path,
> using the same code that the remote driver client would on the remote
> host's build of libvirt.
> 
> Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
> ---
>   build-aux/syntax-check.mk      |   2 +-
>   libvirt.spec.in                |   2 +
>   po/POTFILES.in                 |   1 +
>   src/remote/meson.build         |  17 ++
>   src/remote/remote_ssh_helper.c | 425 +++++++++++++++++++++++++++++++++
>   src/rpc/virnetsocket.h         |   1 +
>   6 files changed, 447 insertions(+), 1 deletion(-)
>   create mode 100644 src/remote/remote_ssh_helper.c
> 
> diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk
> index 6eb59cf90e..151f7a4767 100644
> --- a/build-aux/syntax-check.mk
> +++ b/build-aux/syntax-check.mk
> @@ -1864,7 +1864,7 @@ sc_group-qemu-caps:
>   # List all syntax-check exemptions:
>   exclude_file_name_regexp--sc_avoid_strcase = ^tools/vsh\.h$$
>   
> -_src1=libvirt-stream|qemu/qemu_monitor|util/vir(command|file|fdstream)|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon
> +_src1=libvirt-stream|qemu/qemu_monitor|util/vir(command|file|fdstream)|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon|logging/log_daemon|remote/remote_ssh_helper
>   _test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock|commandhelper
>   exclude_file_name_regexp--sc_avoid_write = \
>     ^(src/($(_src1))|tools/virsh-console|tests/($(_test1)))\.c$$
> diff --git a/libvirt.spec.in b/libvirt.spec.in
> index e64cfdb561..35125e4f8e 100644
> --- a/libvirt.spec.in
> +++ b/libvirt.spec.in
> @@ -1561,6 +1561,8 @@ exit 0
>   
>   %attr(0755, root, root) %{_libexecdir}/libvirt_iohelper
>   
> +%attr(0755, root, root) %{_bindir}/virt-ssh-helper
> +
>   %attr(0755, root, root) %{_sbindir}/libvirtd
>   %attr(0755, root, root) %{_sbindir}/virtproxyd
>   %attr(0755, root, root) %{_sbindir}/virtlogd
> diff --git a/po/POTFILES.in b/po/POTFILES.in
> index c4197604ef..1ab94972c7 100644
> --- a/po/POTFILES.in
> +++ b/po/POTFILES.in
> @@ -182,6 +182,7 @@
>   @SRCDIR at src/remote/remote_daemon_stream.c
>   @SRCDIR at src/remote/remote_driver.c
>   @SRCDIR at src/remote/remote_sockets.c
> + at SRCDIR@src/remote/remote_ssh_helper.c
>   @SRCDIR at src/rpc/virkeepalive.c
>   @SRCDIR at src/rpc/virnetclient.c
>   @SRCDIR at src/rpc/virnetclientprogram.c
> diff --git a/src/remote/meson.build b/src/remote/meson.build
> index 91dd587cba..9ad2f6ab1c 100644
> --- a/src/remote/meson.build
> +++ b/src/remote/meson.build
> @@ -51,6 +51,15 @@ remote_daemon_sources = files(
>   
>   remote_daemon_generated = []
>   
> +virt_ssh_helper_sources = files(
> +  'remote_sockets.c',
> +  'remote_ssh_helper.c',
> +)
> +
> +virt_ssh_helper_dep = [
> +  src_dep,
> +]
> +
>   foreach name : [ 'remote', 'qemu', 'lxc' ]
>     protocol_x = '@0 at _protocol.x'.format(name)
>     dispatch_h = '@0 at _daemon_dispatch_stubs.h'.format(name)
> @@ -278,6 +287,14 @@ if conf.has('WITH_REMOTE')
>           rename: [ '50-libvirt.rules' ],
>         )
>       endif
> +
> +    virt_helpers += {
> +      'name': 'virt-ssh-helper',
> +      'sources': [
> +        virt_ssh_helper_sources
> +      ],
> +      'install_dir': bindir,
> +    }
>     endif
>   endif
>   
> diff --git a/src/remote/remote_ssh_helper.c b/src/remote/remote_ssh_helper.c
> new file mode 100644
> index 0000000000..0da55c1d1f
> --- /dev/null
> +++ b/src/remote/remote_ssh_helper.c
> @@ -0,0 +1,425 @@
> +/*
> + * remote_ssh_helper.c: a netcat replacement for proxying ssh tunnel to daemon
> + *
> + * Copyright (C) 2020 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library.  If not, see
> + * <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <config.h>
> +
> +#include <unistd.h>
> +
> +#include "rpc/virnetsocket.h"
> +#include "viralloc.h"
> +#include "virlog.h"
> +#include "virgettext.h"
> +#include "virfile.h"
> +
> +#include "remote_sockets.h"
> +
> +#define VIR_FROM_THIS VIR_FROM_REMOTE
> +
> +VIR_LOG_INIT("remote.remote_ssh_helper");
> +
> +struct virRemoteSSHHelperBuffer {
> +    size_t length;
> +    size_t offset;
> +    char *data;
> +};
> +
> +typedef struct virRemoteSSHHelper virRemoteSSHHelper;
> +typedef virRemoteSSHHelper *virRemoteSSHHelperPtr;
> +struct virRemoteSSHHelper {
> +    bool quit;
> +    virNetSocketPtr sock;
> +    int stdinWatch;
> +    int stdoutWatch;
> +
> +    struct virRemoteSSHHelperBuffer sockToTerminal;
> +    struct virRemoteSSHHelperBuffer terminalToSock;
> +};
> +
> +
> +static void
> +virRemoteSSHHelperShutdown(virRemoteSSHHelperPtr proxy)
> +{
> +    if (proxy->sock) {
> +        virNetSocketRemoveIOCallback(proxy->sock);
> +        virNetSocketClose(proxy->sock);
> +        virObjectUnref(proxy->sock);
> +        proxy->sock = NULL;
> +    }
> +    VIR_FREE(proxy->sockToTerminal.data);
> +    VIR_FREE(proxy->terminalToSock.data);
> +    if (proxy->stdinWatch != -1)
> +        virEventRemoveHandle(proxy->stdinWatch);
> +    if (proxy->stdoutWatch != -1)
> +        virEventRemoveHandle(proxy->stdoutWatch);
> +    proxy->stdinWatch = -1;
> +    proxy->stdoutWatch = -1;
> +    if (!proxy->quit)
> +        proxy->quit = true;
> +}
> +
> +
> +static void
> +virRemoteSSHHelperEventOnSocket(virNetSocketPtr sock,
> +                                int events,
> +                                void *opaque)
> +{
> +    virRemoteSSHHelperPtr proxy = opaque;
> +
> +    /* we got late event after proxy was shutdown */
> +    if (!proxy->sock)
> +        return;
> +
> +    if (events & VIR_EVENT_HANDLE_READABLE) {
> +        size_t avail = proxy->sockToTerminal.length -
> +            proxy->sockToTerminal.offset;
> +        int got;
> +
> +        if (avail < 1024) {
> +            if (VIR_REALLOC_N(proxy->sockToTerminal.data,
> +                              proxy->sockToTerminal.length + 1024) < 0) {
> +                virRemoteSSHHelperShutdown(proxy);
> +                return;
> +            }
> +            proxy->sockToTerminal.length += 1024;
> +            avail += 1024;
> +        }
> +
> +        got = virNetSocketRead(sock,
> +                               proxy->sockToTerminal.data +
> +                               proxy->sockToTerminal.offset,
> +                               avail);
> +        if (got == -2)
> +            return; /* blocking */
> +        if (got == 0) {
> +            VIR_DEBUG("EOF on socket, shutting down");
> +            virRemoteSSHHelperShutdown(proxy);
> +            return;
> +        }
> +        if (got < 0) {
> +            virRemoteSSHHelperShutdown(proxy);
> +            return;
> +        }
> +        proxy->sockToTerminal.offset += got;
> +        if (proxy->sockToTerminal.offset)
> +            virEventUpdateHandle(proxy->stdoutWatch,
> +                                 VIR_EVENT_HANDLE_WRITABLE);
> +    }
> +
> +    if (events & VIR_EVENT_HANDLE_WRITABLE &&
> +        proxy->terminalToSock.offset) {
> +        ssize_t done;
> +        size_t avail;
> +        done = virNetSocketWrite(proxy->sock,
> +                                 proxy->terminalToSock.data,
> +                                 proxy->terminalToSock.offset);
> +        if (done == -2)
> +            return; /* blocking */
> +        if (done < 0) {
> +            virRemoteSSHHelperShutdown(proxy);
> +            return;
> +        }
> +        memmove(proxy->terminalToSock.data,
> +                proxy->terminalToSock.data + done,
> +                proxy->terminalToSock.offset - done);
> +        proxy->terminalToSock.offset -= done;
> +
> +        avail = proxy->terminalToSock.length - proxy->terminalToSock.offset;
> +        if (avail > 1024) {
> +            ignore_value(VIR_REALLOC_N(proxy->terminalToSock.data,
> +                                       proxy->terminalToSock.offset + 1024));
> +            proxy->terminalToSock.length = proxy->terminalToSock.offset + 1024;
> +        }
> +    }
> +    if (!proxy->terminalToSock.offset)
> +        virNetSocketUpdateIOCallback(proxy->sock,
> +                                     VIR_EVENT_HANDLE_READABLE);
> +
> +    if (events & VIR_EVENT_HANDLE_ERROR ||
> +        events & VIR_EVENT_HANDLE_HANGUP) {
> +        virRemoteSSHHelperShutdown(proxy);
> +    }
> +}
> +
> +
> +static void
> +virRemoteSSHHelperEventOnStdin(int watch G_GNUC_UNUSED,
> +                               int fd G_GNUC_UNUSED,
> +                               int events,
> +                               void *opaque)
> +{
> +    virRemoteSSHHelperPtr proxy = opaque;
> +
> +    /* we got late event after console was shutdown */
> +    if (!proxy->sock)
> +        return;
> +
> +    if (events & VIR_EVENT_HANDLE_READABLE) {
> +        size_t avail = proxy->terminalToSock.length -
> +            proxy->terminalToSock.offset;
> +        int got;
> +
> +        if (avail < 1024) {
> +            if (VIR_REALLOC_N(proxy->terminalToSock.data,
> +                              proxy->terminalToSock.length + 1024) < 0) {
> +                virRemoteSSHHelperShutdown(proxy);
> +                return;
> +            }
> +            proxy->terminalToSock.length += 1024;
> +            avail += 1024;
> +        }
> +
> +        got = read(fd,
> +                   proxy->terminalToSock.data +
> +                   proxy->terminalToSock.offset,
> +                   avail);
> +        if (got < 0) {
> +            if (errno != EAGAIN) {
> +                virReportSystemError(errno, "%s", _("cannot read from stdin"));
> +                virRemoteSSHHelperShutdown(proxy);
> +            }
> +            return;
> +        }
> +        if (got == 0) {
> +            VIR_DEBUG("EOF on stdin, shutting down");
> +            virRemoteSSHHelperShutdown(proxy);
> +            return;
> +        }
> +
> +        proxy->terminalToSock.offset += got;
> +        if (proxy->terminalToSock.offset)
> +            virNetSocketUpdateIOCallback(proxy->sock,
> +                                         VIR_EVENT_HANDLE_READABLE |
> +                                         VIR_EVENT_HANDLE_WRITABLE);
> +    }
> +
> +    if (events & VIR_EVENT_HANDLE_ERROR) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error on stdin"));
> +        virRemoteSSHHelperShutdown(proxy);
> +        return;
> +    }
> +
> +    if (events & VIR_EVENT_HANDLE_HANGUP) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
> +        virRemoteSSHHelperShutdown(proxy);
> +        return;
> +    }
> +}
> +
> +
> +static void
> +virRemoteSSHHelperEventOnStdout(int watch G_GNUC_UNUSED,
> +                                int fd,
> +                                int events,
> +                                void *opaque)
> +{
> +    virRemoteSSHHelperPtr proxy = opaque;
> +
> +    /* we got late event after console was shutdown */
> +    if (!proxy->sock)
> +        return;
> +
> +    if (events & VIR_EVENT_HANDLE_WRITABLE &&
> +        proxy->sockToTerminal.offset) {
> +        ssize_t done;
> +        size_t avail;
> +        done = write(fd,
> +                     proxy->sockToTerminal.data,
> +                     proxy->sockToTerminal.offset);
> +        if (done < 0) {
> +            if (errno != EAGAIN) {
> +                virReportSystemError(errno, "%s", _("cannot write to stdout"));
> +                virRemoteSSHHelperShutdown(proxy);
> +            }
> +            return;
> +        }
> +        memmove(proxy->sockToTerminal.data,
> +                proxy->sockToTerminal.data + done,
> +                proxy->sockToTerminal.offset - done);
> +        proxy->sockToTerminal.offset -= done;
> +
> +        avail = proxy->sockToTerminal.length - proxy->sockToTerminal.offset;
> +        if (avail > 1024) {
> +            ignore_value(VIR_REALLOC_N(proxy->sockToTerminal.data,
> +                                       proxy->sockToTerminal.offset + 1024));
> +            proxy->sockToTerminal.length = proxy->sockToTerminal.offset + 1024;
> +        }
> +    }
> +
> +    if (!proxy->sockToTerminal.offset)
> +        virEventUpdateHandle(proxy->stdoutWatch, 0);
> +
> +    if (events & VIR_EVENT_HANDLE_ERROR) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error stdout"));
> +        virRemoteSSHHelperShutdown(proxy);
> +        return;
> +    }
> +
> +    if (events & VIR_EVENT_HANDLE_HANGUP) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdout"));
> +        virRemoteSSHHelperShutdown(proxy);
> +        return;
> +    }
> +}
> +
> +
> +static int
> +virRemoteSSHHelperRun(virNetSocketPtr sock)
> +{
> +    int ret = -1;
> +    virRemoteSSHHelper proxy = {
> +        .sock = sock,
> +        .stdinWatch = -1,
> +        .stdoutWatch = -1,
> +    };
> +
> +    virEventRegisterDefaultImpl();
> +
> +    if ((proxy.stdinWatch = virEventAddHandle(STDIN_FILENO,
> +                                              VIR_EVENT_HANDLE_READABLE,
> +                                              virRemoteSSHHelperEventOnStdin,
> +                                              &proxy,
> +                                              NULL)) < 0)
> +        goto cleanup;
> +
> +    if ((proxy.stdoutWatch = virEventAddHandle(STDOUT_FILENO,
> +                                               0,
> +                                               virRemoteSSHHelperEventOnStdout,
> +                                               &proxy,
> +                                               NULL)) < 0)
> +        goto cleanup;
> +
> +    if (virNetSocketAddIOCallback(proxy.sock,
> +                                  VIR_EVENT_HANDLE_READABLE,
> +                                  virRemoteSSHHelperEventOnSocket,
> +                                  &proxy,
> +                                  NULL) < 0)
> +        goto cleanup;
> +
> +    while (!proxy.quit)
> +        virEventRunDefaultImpl();
> +
> +    if (virGetLastErrorCode() != VIR_ERR_OK)
> +        goto cleanup;
> +
> +    ret = 0;
> + cleanup:
> +    if (proxy.stdinWatch != -1)
> +        virEventRemoveHandle(proxy.stdinWatch);
> +    if (proxy.stdoutWatch != -1)
> +        virEventRemoveHandle(proxy.stdoutWatch);
> +    return ret;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    const char *uri_str = NULL;
> +    g_autoptr(virURI) uri = NULL;
> +    g_autofree char *driver = NULL;
> +    remoteDriverTransport transport;
> +    bool user = false;
> +    bool autostart = false;
> +    gboolean version = false;
> +    gboolean readonly = false;
> +    g_autofree char *sock_path = NULL;
> +    g_autofree char *daemon_name = NULL;
> +    g_autoptr(virNetSocket) sock = NULL;
> +    GError *error = NULL;
> +    g_autoptr(GOptionContext) context = NULL;
> +    GOptionEntry entries[] = {
> +        { "readonly", 'r', 0, G_OPTION_ARG_NONE, &readonly, "Connect read-only", NULL },
> +        { "version", 'V', 0, G_OPTION_ARG_NONE, &version, "Display version information", NULL },
> +        { NULL, '\0', 0, 0, NULL, NULL, NULL }
> +    };
> +
> +    context = g_option_context_new("- libvirt socket proxy");
> +    g_option_context_add_main_entries(context, entries, PACKAGE);
> +    if (!g_option_context_parse(context, &argc, &argv, &error)) {
> +        g_printerr(_("option parsing failed: %s\n"), error->message);
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    if (version) {
> +        g_print("%s (%s) %s\n", argv[0], PACKAGE_NAME, PACKAGE_VERSION);
> +        exit(EXIT_SUCCESS);
> +    }
> +
> +    virSetErrorFunc(NULL, NULL);
> +    virSetErrorLogPriorityFunc(NULL);
> +
> +    if (virGettextInitialize() < 0 ||
> +        virErrorInitialize() < 0) {
> +        g_printerr(_("%s: initialization failed\n"), argv[0]);
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    virFileActivateDirOverrideForProg(argv[0]);
> +
> +    /* Initialize the log system */
> +    virLogSetFromEnv();
> +
> +    if (optind != (argc - 1)) {
> +        g_printerr("%s: expected a URI\n", argv[0]);
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    uri_str = argv[optind];
> +    VIR_DEBUG("Using URI %s", uri_str);
> +
> +    if (!(uri = virURIParse(uri_str))) {
> +        g_printerr(("%s: cannot parse '%s': %s\n"),
> +                   argv[0], uri_str, virGetLastErrorMessage());
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    if (remoteSplitURIScheme(uri, &driver, &transport) < 0) {
> +        g_printerr(_("%s: cannot parse URI transport '%s': %s\n"),
> +                   argv[0], uri_str, virGetLastErrorMessage());
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    if (transport != REMOTE_DRIVER_TRANSPORT_UNIX) {
> +        g_printerr(_("%s: unexpected URI transport '%s'\n"),
> +                   argv[0], uri_str);
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    remoteGetURIDaemonInfo(uri, transport, &user, &autostart);
> +
> +    sock_path = remoteGetUNIXSocket(transport,
> +                                    REMOTE_DRIVER_MODE_AUTO,
> +                                    driver,
> +                                    !!readonly,
> +                                    user,
> +                                    &daemon_name);
> +
> +    if (virNetSocketNewConnectUNIX(sock_path, autostart, daemon_name, &sock) < 0) {
> +        g_printerr(_("%s: cannot connect to '%s': %s\n"),
> +                   argv[0], sock_path, virGetLastErrorMessage());
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    if (virRemoteSSHHelperRun(sock) < 0) {
> +        g_printerr(_("%s: could not proxy traffic: %s\n"),
> +                   argv[0], virGetLastErrorMessage());
> +        exit(EXIT_FAILURE);
> +    }
> +
> +    exit(EXIT_SUCCESS);
> +}
> diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h
> index d39b270480..3996d264fb 100644
> --- a/src/rpc/virnetsocket.h
> +++ b/src/rpc/virnetsocket.h
> @@ -34,6 +34,7 @@
>   typedef struct _virNetSocket virNetSocket;
>   typedef virNetSocket *virNetSocketPtr;
>   
> +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetSocket, virObjectUnref);
>   
>   typedef void (*virNetSocketIOFunc)(virNetSocketPtr sock,
>                                      int events,
> 




More information about the libvir-list mailing list