[libvirt PATCH 7/9] remote: introduce virtd-nc helper binary

Daniel P. Berrangé berrange at redhat.com
Thu Jul 9 18:36:44 UTC 2020


When accessing libvirtd over a SSH tunnel, the remote driver must spawn
the remote 'nc' process, 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)

 - We can not able to autospawn the libvirtd daemon for session mode
   access

To address these problems this patch introduces the 'virtd-nc' helper
program which takes the URI for the remote driver as a CLI parameter.
It then figures out the socket path to connect to using the same
code as the remote driver does on the remote host.

Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
---
 build-aux/syntax-check.mk  |   2 +-
 po/POTFILES.in             |   1 +
 src/remote/Makefile.inc.am |  30 +++
 src/remote/remote_nc.c     | 424 +++++++++++++++++++++++++++++++++++++
 src/rpc/virnetsocket.h     |   1 +
 5 files changed, 457 insertions(+), 1 deletion(-)
 create mode 100644 src/remote/remote_nc.c

diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk
index d47a92b530..81b307ebe8 100644
--- a/build-aux/syntax-check.mk
+++ b/build-aux/syntax-check.mk
@@ -1967,7 +1967,7 @@ 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_nc
 _test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock|commandhelper
 exclude_file_name_regexp--sc_avoid_write = \
   ^(src/($(_src1))|tools/virsh-console|tests/($(_test1)))\.c$$
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8fd391a63a..8fa47ec276 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -180,6 +180,7 @@
 @SRCDIR@/src/remote/remote_daemon_dispatch.c
 @SRCDIR@/src/remote/remote_daemon_stream.c
 @SRCDIR@/src/remote/remote_driver.c
+ at SRCDIR@/src/remote/remote_nc.c
 @SRCDIR@/src/remote/remote_sockets.c
 @SRCDIR@/src/rpc/virkeepalive.c
 @SRCDIR@/src/rpc/virnetclient.c
diff --git a/src/remote/Makefile.inc.am b/src/remote/Makefile.inc.am
index 0ae97f4107..2527cc193f 100644
--- a/src/remote/Makefile.inc.am
+++ b/src/remote/Makefile.inc.am
@@ -221,6 +221,8 @@ if WITH_LIBVIRTD
 
 sbin_PROGRAMS += libvirtd virtproxyd
 
+libexec_PROGRAMS += virt-nc
+
 augeas_DATA += \
 	remote/libvirtd.aug \
 	remote/virtproxyd.aug \
@@ -286,6 +288,34 @@ remote/virtproxyd.conf: remote/libvirtd.conf.in
 		-e 's/[@]DAEMON_NAME[@]/virtproxyd/' \
 		$< > $@
 
+virt_nc_SOURCES = \
+	remote/remote_sockets.h \
+	remote/remote_sockets.c \
+	remote/remote_nc.c \
+	$(NULL)
+
+virt_nc_CFLAGS = \
+	$(LIBXML_CFLAGS) \
+	$(GLIB_CFLAGS) \
+	$(WARN_CFLAGS) \
+	$(PIE_CFLAGS) \
+	-I$(srcdir)/access \
+	-I$(srcdir)/rpc \
+	$(NULL)
+
+virt_nc_LDFLAGS = \
+	$(RELRO_LDFLAGS) \
+	$(PIE_LDFLAGS) \
+	$(NO_INDIRECT_LDFLAGS) \
+	$(NO_UNDEFINED_LDFLAGS) \
+	$(NULL)
+
+virt_nc_LDADD = \
+	libvirt.la \
+	$(LIBXML_LIBS) \
+	$(NULL)
+
+
 INSTALL_DATA_DIRS += remote
 
 install-data-remote:
diff --git a/src/remote/remote_nc.c b/src/remote/remote_nc.c
new file mode 100644
index 0000000000..d304db1a04
--- /dev/null
+++ b/src/remote/remote_nc.c
@@ -0,0 +1,424 @@
+/*
+ * remote_nc.c: a netcat equivalent for remote driver tunnelling
+ *
+ * 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 "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_nc");
+
+struct virRemoteProxyBuffer {
+    size_t length;
+    size_t offset;
+    char *data;
+};
+
+typedef struct virRemoteProxy virRemoteProxy;
+typedef virRemoteProxy *virRemoteProxyPtr;
+struct virRemoteProxy {
+    bool quit;
+    virNetSocketPtr sock;
+    int stdinWatch;
+    int stdoutWatch;
+
+    struct virRemoteProxyBuffer sockToTerminal;
+    struct virRemoteProxyBuffer terminalToSock;
+};
+
+
+static void
+virRemoteProxyShutdown(virRemoteProxyPtr 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
+virRemoteProxyEventOnSocket(virNetSocketPtr sock,
+                            int events, void *opaque)
+{
+    virRemoteProxyPtr 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) {
+                virRemoteProxyShutdown(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");
+            virRemoteProxyShutdown(proxy);
+            return;
+        }
+        if (got < 0) {
+            virRemoteProxyShutdown(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) {
+            virRemoteProxyShutdown(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) {
+        virRemoteProxyShutdown(proxy);
+    }
+}
+
+
+static void
+virRemoteProxyEventOnStdin(int watch G_GNUC_UNUSED,
+                           int fd G_GNUC_UNUSED,
+                           int events,
+                           void *opaque)
+{
+    virRemoteProxyPtr 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) {
+                virRemoteProxyShutdown(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"));
+                virRemoteProxyShutdown(proxy);
+            }
+            return;
+        }
+        if (got == 0) {
+            VIR_DEBUG("EOF on stdin, shutting down");
+            virRemoteProxyShutdown(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"));
+        virRemoteProxyShutdown(proxy);
+        return;
+    }
+
+    if (events & VIR_EVENT_HANDLE_HANGUP) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
+        virRemoteProxyShutdown(proxy);
+        return;
+    }
+}
+
+
+static void
+virRemoteProxyEventOnStdout(int watch G_GNUC_UNUSED,
+                            int fd,
+                            int events,
+                            void *opaque)
+{
+    virRemoteProxyPtr 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"));
+                virRemoteProxyShutdown(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"));
+        virRemoteProxyShutdown(proxy);
+        return;
+    }
+
+    if (events & VIR_EVENT_HANDLE_HANGUP) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdout"));
+        virRemoteProxyShutdown(proxy);
+        return;
+    }
+}
+
+
+static int
+virRemoteProxyRun(virNetSocketPtr sock)
+{
+    int ret = -1;
+    virRemoteProxy proxy = {
+        .sock = sock,
+        .stdinWatch = -1,
+        .stdoutWatch = -1,
+    };
+
+    virEventRegisterDefaultImpl();
+
+    if ((proxy.stdinWatch = virEventAddHandle(STDIN_FILENO,
+                                              VIR_EVENT_HANDLE_READABLE,
+                                              virRemoteProxyEventOnStdin,
+                                              &proxy,
+                                              NULL)) < 0)
+        goto cleanup;
+
+    if ((proxy.stdoutWatch = virEventAddHandle(STDOUT_FILENO,
+                                               0,
+                                               virRemoteProxyEventOnStdout,
+                                               &proxy,
+                                               NULL)) < 0)
+        goto cleanup;
+
+    if (virNetSocketAddIOCallback(proxy.sock,
+                                  VIR_EVENT_HANDLE_READABLE,
+                                  virRemoteProxyEventOnSocket,
+                                  &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 }
+    };
+
+    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 (virRemoteProxyRun(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,
-- 
2.26.2




More information about the libvir-list mailing list