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@xxxxxxxxxx> --- 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 +@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