[libvirt] [PATCHv2 0/5] Add API to allow TCP connection tunelling

This series adds ability for the qemu driver to tunnel connections to TCP ports from the host. This is useful for enabling remote VNC/SPICE sessions without the need to configure SSH tunnels or portforwards and without the need to open the ports for public. There's also an advantage for tools such as virt-viewer that have to guess the remote connection parameters and you have to hope that nothing is in your way. With spice/VNC clients that have support for read/write callbacks, this would allow also direct connection without an intermediate socket. The API and tunelling works but there's no (stable and good working) client for this API. I hacked up a dirty netcat-like terminal into virsh for testing purposes (see patch 5/5) but that isn't what I'd like to see. The client should be able to open a listening socket and when a client connects to it, it opens a stream and connects it to the remote host. For the client there are two options: 1) do all the stuff in virsh: + one tool to rule them all - i'd like to daemonize it and I don't know if that's okay in virsh 2) add a new tool "virtunnel": + less virsh pollution - separate tool ... As nobody responded, I'd like to re-ask for someones opinion on this. (note: this is my personal effort, I'm annoyed of opening ssh tunnels to remote displays on my server and I don't want to open the ports to public. ) After this it would be great to add support for this to virt-viewer. I will have a look at that later. ---- Diff to v1: - fixed error reporting in 2/5 - documented limitation to "localhost" in 3/5 - fixed possible segfault in 4/5 --- Peter Krempa (5): api: Add API to allow TCP tunneling through streams to the host fdstream: Add support for TCP connections of streams qemu: Add configuration options to enable TCP tunelling qemu: Implement virNodeTunnelTcp for the qemu driver NOT_TO_BE_APPLIED_UPSTREAM: quick and dirty virsh client to test the stuff include/libvirt/libvirt.h.in | 11 +++++++ src/driver.h | 8 +++++ src/fdstream.c | 73 ++++++++++++++++++++++++++++++++++++++++++++ src/fdstream.h | 5 +++ src/libvirt.c | 67 ++++++++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 1 + src/qemu/qemu.conf | 16 ++++++++++ src/qemu/qemu_conf.c | 26 ++++++++++++++++ src/qemu/qemu_conf.h | 13 ++++++++ src/qemu/qemu_driver.c | 48 +++++++++++++++++++++++++++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 9 +++++- src/remote_protocol-structs | 6 ++++ src/rpc/gendispatch.pl | 1 + tools/console.c | 66 +++++++++++++++++++++------------------ tools/console.h | 9 ++---- tools/virsh-domain.c | 17 +++++++++-- tools/virsh-host.c | 60 ++++++++++++++++++++++++++++++++++++ 19 files changed, 397 insertions(+), 41 deletions(-) -- 1.8.0

This patch adds API that will allow to use streams to connect to TCP sockets from the point of view of the host. This API is intended to allow remote display (SPICE/VNC) sessions to managed hosts that prefer not to expose the ports to outer networks and without the need to configure TCP tunneling through ssh or firewalls. The API takes the remote hostname and port as arguments and is configurable using flags. The user may specify to use IPv4 or IPv6 using them. --- include/libvirt/libvirt.h.in | 11 ++++++++ src/driver.h | 8 ++++++ src/libvirt.c | 67 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 9 +++++- src/remote_protocol-structs | 6 ++++ src/rpc/gendispatch.pl | 1 + 8 files changed, 103 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 17804ca..afc5b42 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -3734,6 +3734,17 @@ int virConnectIsEncrypted(virConnectPtr conn); int virConnectIsSecure(virConnectPtr conn); int virConnectIsAlive(virConnectPtr conn); +typedef enum { + VIR_NODE_TUNNEL_TCP_IPV4 = (1 << 0), /* use the IPv4 protocol */ + VIR_NODE_TUNNEL_TCP_IPV6 = (1 << 1), /* use the IPv6 protocol */ +} virNodeTunnelTCPFlags; + +int virNodeTunnelTCP(virConnectPtr conn, + virStreamPtr stream, + const char *address, + const char *service, + unsigned int flags); + /* * CPU specification API */ diff --git a/src/driver.h b/src/driver.h index 64d652f..7c3c5b2 100644 --- a/src/driver.h +++ b/src/driver.h @@ -915,6 +915,13 @@ typedef int unsigned long long minimum, unsigned int flags); +typedef int + (*virDrvNodeTunnelTCP)(virConnectPtr conn, + virStreamPtr stream, + const char *address, + const char *service, + unsigned int flags); + /** * _virDriver: * @@ -1107,6 +1114,7 @@ struct _virDriver { virDrvNodeGetCPUMap nodeGetCPUMap; virDrvDomainFSTrim domainFSTrim; virDrvDomainSendProcessSignal domainSendProcessSignal; + virDrvNodeTunnelTCP nodeTunnelTCP; }; typedef int diff --git a/src/libvirt.c b/src/libvirt.c index 6a7a817..bd4cca9 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -20373,3 +20373,70 @@ error: virDispatchError(dom->conn); return -1; } + +/** + * virNodeTunnelTCP: + * @conn: pointer to the connection object + * @stream: pointer to a stream object + * @address: pointer to a string containing address to connect to + * @service: pointer to a string containing port number or service name + * @flags: bitwise-OR of virNodeTunnelTCPFlags + * + * Creates a TCP connection to the desired host and port and connects + * the virStream to the connection + * + * If @address is NULL the connection is attempted to the "localhost" + * address. + * + * @service may contain a numeric port ID or a service name as in + * /etc/services. + * + * Restrictions: + * This functionality may be disabled in the hypervisor driver. + * Only connections to the host itself may be allowed. + * This functionality may be disabled for readonly connections. + * + * @flags may contain VIR_NODE_TUNNEL_TCP_IPV4 or VIR_NODE_TUNNEL_TCP_IPV6 + * to denote the desired protocol. If none or both of those are specified + * the selected protocol depends on resolution of the host address. + * + * Returns 0 if the connection succeeds; -1 on failure. + */ +int +virNodeTunnelTCP(virConnectPtr conn, + virStreamPtr stream, + const char *address, + const char *service, + unsigned int flags) +{ + int ret = -1; + VIR_DEBUG("conn=%p stream=%p address=(%p)'%s' service=(%p)'%s' flags=%x", + conn, stream, address, NULLSTR(address), + service, NULLSTR(service), flags); + + virCheckNonNullArgGoto(stream, error); + virCheckNonNullArgGoto(service, error); + + virResetLastError(); + + if (!VIR_IS_CONNECT(conn)) { + virLibConnError(VIR_ERR_INVALID_CONN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + if (conn->driver->nodeTunnelTCP) { + ret = conn->driver->nodeTunnelTCP(conn, stream, address, + service, flags); + if (ret < 0) + goto error; + + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(conn); + return ret; +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index e3d63d3..c8a7f1c 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -578,6 +578,7 @@ LIBVIRT_1.0.1 { global: virDomainFSTrim; virDomainSendProcessSignal; + virNodeTunnelTCP; } LIBVIRT_1.0.0; # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 5cc7e32..ef96f51 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6153,6 +6153,7 @@ static virDriver remote_driver = { .nodeGetMemoryParameters = remoteNodeGetMemoryParameters, /* 0.10.2 */ .nodeGetCPUMap = remoteNodeGetCPUMap, /* 1.0.0 */ .domainFSTrim = remoteDomainFSTrim, /* 1.0.1 */ + .nodeTunnelTCP = remoteNodeTunnelTCP, /* 1.0.1 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index bdad9f0..0a64d56 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -2696,6 +2696,12 @@ struct remote_domain_fstrim_args { unsigned int flags; }; +struct remote_node_tunnel_tcp_args { + remote_string address; + remote_nonnull_string service; + unsigned int flags; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -3042,7 +3048,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_EVENT_PMSUSPEND_DISK = 292, /* autogen autogen */ REMOTE_PROC_NODE_GET_CPU_MAP = 293, /* skipgen skipgen */ REMOTE_PROC_DOMAIN_FSTRIM = 294, /* autogen autogen */ - REMOTE_PROC_DOMAIN_SEND_PROCESS_SIGNAL = 295 /* autogen autogen */ + REMOTE_PROC_DOMAIN_SEND_PROCESS_SIGNAL = 295, /* autogen autogen */ + REMOTE_PROC_NODE_TUNNEL_TCP = 296 /* autogen autogen | readstream@1 */ /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index e7d05b8..5e04bd3 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2151,6 +2151,11 @@ struct remote_domain_fstrim_args { uint64_t minimum; u_int flags; }; +struct remote_node_tunnel_tcp_args { + remote_string address; + remote_nonnull_string service; + u_int flags; +}; enum remote_procedure { REMOTE_PROC_OPEN = 1, REMOTE_PROC_CLOSE = 2, @@ -2447,4 +2452,5 @@ enum remote_procedure { REMOTE_PROC_NODE_GET_CPU_MAP = 293, REMOTE_PROC_DOMAIN_FSTRIM = 294, REMOTE_PROC_DOMAIN_SEND_PROCESS_SIGNAL = 295, + REMOTE_PROC_NODE_TUNNEL_TCP = 296, }; diff --git a/src/rpc/gendispatch.pl b/src/rpc/gendispatch.pl index 899f4bc..902acd8 100755 --- a/src/rpc/gendispatch.pl +++ b/src/rpc/gendispatch.pl @@ -45,6 +45,7 @@ sub fixup_name { $name =~ s/Nmi$/NMI/; $name =~ s/Pm/PM/; $name =~ s/Fstrim$/FSTrim/; + $name =~ s/Tcp$/TCP/; return $name; } -- 1.8.0

This patch adds the backend stuff to enable connecting virStreams to TCP sockets. This patch adds a helper virFDStreamConnectTCP() that does the hostname resolution and prepares the filedescriptor for the stream. --- src/fdstream.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ src/fdstream.h | 5 ++++ src/libvirt_private.syms | 1 + 3 files changed, 79 insertions(+) diff --git a/src/fdstream.c b/src/fdstream.c index d1eb04c..f026485 100644 --- a/src/fdstream.c +++ b/src/fdstream.c @@ -31,6 +31,7 @@ # include <sys/un.h> #endif #include <netinet/in.h> +#include <netdb.h> #include "fdstream.h" #include "virterror_internal.h" @@ -565,6 +566,78 @@ int virFDStreamConnectUNIX(virStreamPtr st ATTRIBUTE_UNUSED, } #endif +int +virFDStreamConnectTCP(virStreamPtr st, + const char *address, + const char *service, + unsigned int flags) +{ + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *rp; + struct sockaddr_in sa; + + int fd = -1; + int ret = -1; + int rc; + int err = 0; + + memset(&sa, 0, sizeof(sa)); + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; /* TCP connections */ + hints.ai_family = AF_UNSPEC; /* allow IPv4 and IPv6 */ + + if (flags & VIR_NODE_TUNNEL_TCP_IPV4 && + !(flags & VIR_NODE_TUNNEL_TCP_IPV6)) + hints.ai_family = AF_INET; + + if (flags & VIR_NODE_TUNNEL_TCP_IPV6 && + !(flags & VIR_NODE_TUNNEL_TCP_IPV4)) + hints.ai_family = AF_INET6; + + if ((rc = getaddrinfo(address, service, &hints, &res)) != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to reslove address '%s' and service '%s': %s"), + address, service, gai_strerror(rc)); + goto cleanup; + } + + /* try to connect to the remote service */ + for (rp = res; rp != NULL; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + err = errno; + continue; + } + + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) { + /* success */ + break; + } + + err = errno; + VIR_FORCE_CLOSE(fd); + } + + if (rp == NULL && fd == -1) { + virReportSystemError(err, _("Failed to connect to service '%s' " + "at node '%s'"), service, address); + goto cleanup; + } + + if (virFDStreamOpenInternal(st, fd, NULL, -1, 0) < 0) { + VIR_FORCE_CLOSE(fd); + goto cleanup; + } + + ret = 0; + +cleanup: + freeaddrinfo(res); + + return ret; +} + static int virFDStreamOpenFileInternal(virStreamPtr st, const char *path, diff --git a/src/fdstream.h b/src/fdstream.h index 65457d8..296b53f 100644 --- a/src/fdstream.h +++ b/src/fdstream.h @@ -40,6 +40,11 @@ int virFDStreamConnectUNIX(virStreamPtr st, const char *path, bool abstract); +int virFDStreamConnectTCP(virStreamPtr stream, + const char *address, + const char *service, + unsigned int flags); + int virFDStreamOpenFile(virStreamPtr st, const char *path, unsigned long long offset, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 499ab3b..88b4338 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -636,6 +636,7 @@ virEventPollUpdateTimeout; # fdstream.h virFDStreamOpen; +virFDStreamConnectTCP; virFDStreamConnectUNIX; virFDStreamOpenFile; virFDStreamCreateFile; -- 1.8.0

This patch adds configuration options for the qemu driver to control the behavior of the TCP tunelling API. The behavior can be configured separately for read-write connections and for read-only connections enabling finer granularity of control. --- src/qemu/qemu.conf | 16 ++++++++++++++++ src/qemu/qemu_conf.c | 26 ++++++++++++++++++++++++++ src/qemu/qemu_conf.h | 13 +++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index dd853c8..9064b33 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -153,6 +153,22 @@ #remote_display_port_min = 5900 #remote_display_port_max = 65535 +## +# TCP tunneling +# +# Libvirt supports TCP tunneling using libvirt's streams. This can be used +# to forward graphical display and other connections from remote clients +# to the host machine. This might pose a security risk so the tunneling +# option is disabled by default. +# +# Possible values are: "disable" - don't allow any tcp tunnels +# "local" - allow connections to "localhost" address +# "enable" - allow tunneling to any node +# +# Configuration of forwarding for read-write connections: +#tunnel_tcp_rw = "enable" +# Configuration of forwarding for read-only connections: +#tunnel_tcp_ro = "local" # The default security driver is SELinux. If SELinux is disabled # on the host, then the security driver will automatically disable diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 8d380a1..396e5d9 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -57,6 +57,11 @@ #define VIR_FROM_THIS VIR_FROM_QEMU +VIR_ENUM_IMPL(qemuTunnelTcpConfig, QEMU_TCP_TUNNEL_LAST, + "disable", + "local", + "enable"); + struct _qemuDriverCloseDef { virConnectPtr conn; qemuDriverCloseCallback cb; @@ -78,6 +83,7 @@ int qemuLoadDriverConfig(virQEMUDriverPtr driver, virConfValuePtr p; char *user = NULL; char *group = NULL; + char *tmp; int ret = -1; int i; @@ -375,6 +381,26 @@ int qemuLoadDriverConfig(virQEMUDriverPtr driver, GET_VALUE_LONG("keepalive_count", driver->keepAliveCount); GET_VALUE_LONG("seccomp_sandbox", driver->seccompSandbox); + tmp = NULL; + GET_VALUE_STR("tunnel_tcp_ro", tmp); + if (tmp && + (driver->tunnelTcpRo = qemuTunnelTcpConfigTypeFromString(tmp)) < 0) { + virReportError(VIR_ERR_CONF_SYNTAX, + _("Invalid value '%s' for config option tunnel_tcp_ro"), + tmp); + goto cleanup; + } + + tmp = NULL; + GET_VALUE_STR("tunnel_tcp_rw", tmp); + if (tmp && + (driver->tunnelTcpRw = qemuTunnelTcpConfigTypeFromString(tmp)) < 0) { + virReportError(VIR_ERR_CONF_SYNTAX, + _("Invalid value '%s' for config option tunnel_tcp_rw"), + tmp); + goto cleanup; + } + ret = 0; cleanup: diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index d0d25ce..0d2d66b 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -163,8 +163,21 @@ struct _virQEMUDriver { int keepAliveInterval; unsigned int keepAliveCount; int seccompSandbox; + + int tunnelTcpRo; + int tunnelTcpRw; }; +enum qemuTunnelTcpConfigType { + QEMU_TCP_TUNNEL_DISABLE = 0, + QEMU_TCP_TUNNEL_LOCAL, + QEMU_TCP_TUNNEL_ENABLE, + + QEMU_TCP_TUNNEL_LAST +}; + +VIR_ENUM_DECL(qemuTunnelTcpConfig); + typedef struct _qemuDomainCmdlineDef qemuDomainCmdlineDef; typedef qemuDomainCmdlineDef *qemuDomainCmdlineDefPtr; struct _qemuDomainCmdlineDef { -- 1.8.0

The driver function implemented by this patch checks the configuration if the connection is allowed and opens the connection to the socked using the fdstream handler. --- src/qemu/qemu_driver.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e099c5c..77a404f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -14868,6 +14868,53 @@ endjob: cleanup: if (vm) virDomainObjUnlock(vm); + + return ret; +} + +static int +nodeTunnelTCP(virConnectPtr conn, + virStreamPtr stream, + const char *address, + const char *service, + unsigned int flags) +{ + virQEMUDriverPtr driver = conn->privateData; + int config = driver->tunnelTcpRw; + int ret = -1; + + virCheckFlags(VIR_NODE_TUNNEL_TCP_IPV4 | + VIR_NODE_TUNNEL_TCP_IPV6, -1); + + if (!address) + address = "localhost"; + + if (conn->flags & VIR_CONNECT_RO) + config = driver->tunnelTcpRo; + + switch (config) { + case QEMU_TCP_TUNNEL_DISABLE: + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("Can't open TCP tunnel: disabled in configuration")); + goto cleanup; + break; + case QEMU_TCP_TUNNEL_LOCAL: + if (STRNEQ(address, "localhost")) { + virReportError(VIR_ERR_INVALID_ARG, + _("Tunneling to host '%s' forbidden. " + "Only connections to 'localhost' are allowed"), + address); + goto cleanup; + } + break; + default: + /* ok - can be only reached if enabled */ + break; + } + + ret = virFDStreamConnectTCP(stream, address, service, flags); + +cleanup: return ret; } @@ -15045,6 +15092,7 @@ static virDriver qemuDriver = { .nodeSetMemoryParameters = nodeSetMemoryParameters, /* 0.10.2 */ .nodeGetCPUMap = nodeGetCPUMap, /* 1.0.0 */ .domainFSTrim = qemuDomainFSTrim, /* 1.0.1 */ + .nodeTunnelTCP = nodeTunnelTCP, /* 1.0.1 */ }; -- 1.8.0

This patch adds a "remote netcat" functionality using for example: virsh tcp-console --host localhost --port 1234 --ipv4 --- tools/console.c | 66 ++++++++++++++++++++++++++++------------------------ tools/console.h | 9 ++----- tools/virsh-domain.c | 17 +++++++++++--- tools/virsh-host.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/tools/console.c b/tools/console.c index 40de8eb..e4e4fd7 100644 --- a/tools/console.c +++ b/tools/console.c @@ -325,38 +325,46 @@ vshMakeStdinRaw(struct termios *ttyattr, bool report_errors) return 0; } -int vshRunConsole(virDomainPtr dom, - const char *dev_name, - const char *escape_seq, - unsigned int flags) +int +vshRunConsole(virStreamPtr st, + const char *escape_seq, + bool raw) { int ret = -1; struct termios ttyattr; - void (*old_sigquit)(int); - void (*old_sigterm)(int); - void (*old_sigint)(int); - void (*old_sighup)(int); - void (*old_sigpipe)(int); + void (*old_sigquit)(int) = NULL; + void (*old_sigterm)(int) = NULL; + void (*old_sigint)(int) = NULL; + void (*old_sighup)(int) = NULL; + void (*old_sigpipe)(int) = NULL; virConsolePtr con = NULL; + if (!st) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid stream")); + return -1; + } + /* Put STDIN into raw mode so that stuff typed does not echo to the screen (the TTY reads will result in it being echoed back already), and also ensure Ctrl-C, etc is blocked, and misc other bits */ - if (vshMakeStdinRaw(&ttyattr, true) < 0) + if (raw && vshMakeStdinRaw(&ttyattr, true) < 0) goto resettty; /* Trap all common signals so that we can safely restore the original terminal settings on STDIN before the process exits - people don't like being left with a messed up terminal ! */ - old_sigquit = signal(SIGQUIT, do_signal); - old_sigterm = signal(SIGTERM, do_signal); - old_sigint = signal(SIGINT, do_signal); - old_sighup = signal(SIGHUP, do_signal); - old_sigpipe = signal(SIGPIPE, do_signal); - got_signal = 0; + if (raw) { + old_sigquit = signal(SIGQUIT, do_signal); + old_sigterm = signal(SIGTERM, do_signal); + old_sigint = signal(SIGINT, do_signal); + old_sighup = signal(SIGHUP, do_signal); + old_sigpipe = signal(SIGPIPE, do_signal); + got_signal = 0; + } if (VIR_ALLOC(con) < 0) { virReportOOMError(); @@ -364,13 +372,8 @@ int vshRunConsole(virDomainPtr dom, } con->escapeChar = vshGetEscapeChar(escape_seq); - con->st = virStreamNew(virDomainGetConnect(dom), - VIR_STREAM_NONBLOCK); - if (!con->st) - goto cleanup; - - if (virDomainOpenConsole(dom, dev_name, con->st, flags) < 0) - goto cleanup; + virStreamRef(st); + con->st = st; if (virCondInit(&con->cond) < 0 || virMutexInit(&con->lock) < 0) goto cleanup; @@ -411,17 +414,20 @@ int vshRunConsole(virDomainPtr dom, VIR_FREE(con); } - /* Restore original signal handlers */ - signal(SIGPIPE, old_sigpipe); - signal(SIGHUP, old_sighup); - signal(SIGINT, old_sigint); - signal(SIGTERM, old_sigterm); - signal(SIGQUIT, old_sigquit); + if (raw) { + /* Restore original signal handlers */ + signal(SIGPIPE, old_sigpipe); + signal(SIGHUP, old_sighup); + signal(SIGINT, old_sigint); + signal(SIGTERM, old_sigterm); + signal(SIGQUIT, old_sigquit); + } resettty: /* Put STDIN back into the (sane?) state we found it in before starting */ - tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr); + if (raw) + tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr); return ret; } diff --git a/tools/console.h b/tools/console.h index 46255b8..c23acda 100644 --- a/tools/console.h +++ b/tools/console.h @@ -3,7 +3,7 @@ * * Copyright (C) 2007, 2010, 2012 Red Hat, Inc. * - * This library is free software; you can redistribute it and/or + * 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. @@ -24,13 +24,8 @@ # define __VIR_CONSOLE_H__ # ifndef WIN32 - # include <termios.h> - -int vshRunConsole(virDomainPtr dom, - const char *dev_name, - const char *escape_seq, - unsigned int flags); +int vshRunConsole(virStreamPtr st, const char *escape_seq, bool raw); int vshMakeStdinRaw(struct termios *ttyattr, bool report_errors); diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 96e62fc..4a500e6 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1887,10 +1887,12 @@ static const vshCmdOptDef opts_console[] = { }; static bool -cmdRunConsole(vshControl *ctl, virDomainPtr dom, +cmdRunConsole(vshControl *ctl, + virDomainPtr dom, const char *name, unsigned int flags) { + virStreamPtr st = NULL; bool ret = false; int state; @@ -1905,17 +1907,26 @@ cmdRunConsole(vshControl *ctl, virDomainPtr dom, } if (!isatty(STDIN_FILENO)) { - vshError(ctl, "%s", _("Cannot run interactive console without a controlling TTY")); + vshError(ctl, "%s", _("Cannot run interactive console " + "without a controlling TTY")); goto cleanup; } + if (!(st = virStreamNew(virDomainGetConnect(dom), VIR_STREAM_NONBLOCK))) + goto cleanup; + + if (virDomainOpenConsole(dom, name, st, flags) < 0) + goto cleanup; + vshPrintExtra(ctl, _("Connected to domain %s\n"), virDomainGetName(dom)); vshPrintExtra(ctl, _("Escape character is %s\n"), ctl->escapeChar); fflush(stdout); - if (vshRunConsole(dom, name, ctl->escapeChar, flags) == 0) + if (vshRunConsole(st, ctl->escapeChar, true) == 0) ret = true; cleanup: + if (st) + virStreamFree(st); return ret; } diff --git a/tools/virsh-host.c b/tools/virsh-host.c index 3d13e01..bf2a48f 100644 --- a/tools/virsh-host.c +++ b/tools/virsh-host.c @@ -31,6 +31,7 @@ #include <libxml/xpath.h> #include <libxml/xmlsave.h> +#include "console.h" #include "internal.h" #include "buf.h" #include "memory.h" @@ -1073,6 +1074,64 @@ error: goto cleanup; } +static const vshCmdInfo info_node_tcp_console[] = { + {"help", N_("Connect to a TCP port on the host using a console")}, + {"desc", N_("Connect to a TCP port on the host using a console")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_node_tcp_console[] = { + {"port", VSH_OT_DATA, VSH_OFLAG_REQ, N_("port to connect to")}, + {"host", VSH_OT_STRING, VSH_OFLAG_NONE, N_("hostname to connect to")}, + {"ipv4", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("use IPv4")}, + {"ipv6", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("use IPv6")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdNodeTCPConsole(vshControl *ctl, const vshCmd *cmd) +{ + bool ret = false; + const char *host; + const char *port; + unsigned int flags = 0; + virStreamPtr st = NULL; + + if (vshCommandOptString(cmd, "host", &host) < 0) { + vshError(ctl, "%s", _("Invalid hostname")); + goto cleanup; + } + + if (vshCommandOptString(cmd, "port", &port) < 0) { + vshError(ctl, "%s", _("Invalid port")); + goto cleanup; + } + + if (vshCommandOptBool(cmd, "ipv4")) + flags |= VIR_NODE_TUNNEL_TCP_IPV4; + + if (vshCommandOptBool(cmd, "ipv6")) + flags |= VIR_NODE_TUNNEL_TCP_IPV6; + + if (!(st = virStreamNew(ctl->conn, VIR_STREAM_NONBLOCK))) + goto cleanup; + + if (virNodeTunnelTCP(ctl->conn, st, host, port, flags) < 0) + goto cleanup; + + vshPrintExtra(ctl, _("Connected to host '%s' port '%s'\n"), host, port); + vshPrintExtra(ctl, _("Escape character is %s\n"), ctl->escapeChar); + fflush(stdout); + if (vshRunConsole(st, ctl->escapeChar, false) == 0) + ret = true; + +cleanup: + if (st) + virStreamFree(st); + + return ret; +} + const vshCmdDef hostAndHypervisorCmds[] = { {"capabilities", cmdCapabilities, NULL, info_capabilities, 0}, {"connect", cmdConnect, opts_connect, info_connect, @@ -1094,5 +1153,6 @@ const vshCmdDef hostAndHypervisorCmds[] = { {"sysinfo", cmdSysinfo, NULL, info_sysinfo, 0}, {"uri", cmdURI, NULL, info_uri, 0}, {"version", cmdVersion, opts_version, info_version, 0}, + {"node-tcp-console", cmdNodeTCPConsole, opts_node_tcp_console, info_node_tcp_console, 0}, {NULL, NULL, NULL, NULL, 0} }; -- 1.8.0

Hi Peter, On Mon, Dec 10, 2012 at 09:29:39AM +0100, Peter Krempa wrote:
This series adds ability for the qemu driver to tunnel connections to TCP ports from the host. This is useful for enabling remote VNC/SPICE sessions without the need to configure SSH tunnels or portforwards and without the need to open the ports for public.
It'd be nice to have this in. Are you still on it? Cheers, -- Guido
There's also an advantage for tools such as virt-viewer that have to guess the remote connection parameters and you have to hope that nothing is in your way. With spice/VNC clients that have support for read/write callbacks, this would allow also direct connection without an intermediate socket.
The API and tunelling works but there's no (stable and good working) client for this API. I hacked up a dirty netcat-like terminal into virsh for testing purposes (see patch 5/5) but that isn't what I'd like to see.
The client should be able to open a listening socket and when a client connects to it, it opens a stream and connects it to the remote host.
For the client there are two options: 1) do all the stuff in virsh: + one tool to rule them all - i'd like to daemonize it and I don't know if that's okay in virsh
2) add a new tool "virtunnel": + less virsh pollution - separate tool ...
As nobody responded, I'd like to re-ask for someones opinion on this.
(note: this is my personal effort, I'm annoyed of opening ssh tunnels to remote displays on my server and I don't want to open the ports to public. )
After this it would be great to add support for this to virt-viewer. I will have a look at that later.
---- Diff to v1: - fixed error reporting in 2/5 - documented limitation to "localhost" in 3/5 - fixed possible segfault in 4/5 ---
Peter Krempa (5): api: Add API to allow TCP tunneling through streams to the host fdstream: Add support for TCP connections of streams qemu: Add configuration options to enable TCP tunelling qemu: Implement virNodeTunnelTcp for the qemu driver NOT_TO_BE_APPLIED_UPSTREAM: quick and dirty virsh client to test the stuff
include/libvirt/libvirt.h.in | 11 +++++++ src/driver.h | 8 +++++ src/fdstream.c | 73 ++++++++++++++++++++++++++++++++++++++++++++ src/fdstream.h | 5 +++ src/libvirt.c | 67 ++++++++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 1 + src/qemu/qemu.conf | 16 ++++++++++ src/qemu/qemu_conf.c | 26 ++++++++++++++++ src/qemu/qemu_conf.h | 13 ++++++++ src/qemu/qemu_driver.c | 48 +++++++++++++++++++++++++++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 9 +++++- src/remote_protocol-structs | 6 ++++ src/rpc/gendispatch.pl | 1 + tools/console.c | 66 +++++++++++++++++++++------------------ tools/console.h | 9 ++---- tools/virsh-domain.c | 17 +++++++++-- tools/virsh-host.c | 60 ++++++++++++++++++++++++++++++++++++ 19 files changed, 397 insertions(+), 41 deletions(-)
-- 1.8.0
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 02/25/13 14:51, Guido Günther wrote:
Hi Peter, On Mon, Dec 10, 2012 at 09:29:39AM +0100, Peter Krempa wrote:
This series adds ability for the qemu driver to tunnel connections to TCP ports from the host. This is useful for enabling remote VNC/SPICE sessions without the need to configure SSH tunnels or portforwards and without the need to open the ports for public.
It'd be nice to have this in. Are you still on it? Cheers, -- Guido
I'm planing to respin this in some time with a few changes. I will probably not allow to tunnel arbitrary ports from the machine libvirt is running on but limit it to a few selected ones controllable by flags. (The general approach did not get accepted very well) Peter
participants (2)
-
Guido Günther
-
Peter Krempa