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(a)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(a)/src/remote/remote_daemon_dispatch.c
@SRCDIR(a)/src/remote/remote_daemon_stream.c
@SRCDIR(a)/src/remote/remote_driver.c
+@SRCDIR(a)/src/remote/remote_nc.c
@SRCDIR(a)/src/remote/remote_sockets.c
@SRCDIR(a)/src/rpc/virkeepalive.c
@SRCDIR(a)/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