mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-22 13:45:38 +00:00
remote: introduce virt-ssh-helper binary
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. 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@redhat.com>
This commit is contained in:
parent
1ba30bc1e5
commit
dfad1b551d
@ -1872,7 +1872,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$$
|
||||
|
@ -1557,6 +1557,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
|
||||
|
@ -184,6 +184,7 @@
|
||||
@SRCDIR@src/remote/remote_daemon_stream.c
|
||||
@SRCDIR@src/remote/remote_driver.c
|
||||
@SRCDIR@src/remote/remote_sockets.c
|
||||
@SRCDIR@src/remote/remote_ssh_helper.c
|
||||
@SRCDIR@src/rpc/virkeepalive.c
|
||||
@SRCDIR@src/rpc/virnetclient.c
|
||||
@SRCDIR@src/rpc/virnetclientprogram.c
|
||||
|
@ -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@_protocol.x'.format(name)
|
||||
dispatch_h = '@0@_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
|
||||
|
||||
|
425
src/remote/remote_ssh_helper.c
Normal file
425
src/remote/remote_ssh_helper.c
Normal file
@ -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);
|
||||
}
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user