[virt-tools-list] [PATCH 3 of 5] console: factor out ssh tunnel code, add multi-connexion Tunnels class
Marc-André Lureau
marcandre.lureau at redhat.com
Wed Dec 22 09:56:07 UTC 2010
# HG changeset patch
# User Marc-André Lureau <marcandre.lureau at redhat.com>
# Date 1292967276 -3600
# Node ID a52da458b2bef05146d2f127e8fa51743a0ae4a3
# Parent 2b694c7d07ecd89cc00a5dc2556544677adb4bc8
console: factor out ssh tunnel code, add multi-connexion Tunnels class
diff -r 2b694c7d07ec -r a52da458b2be src/virtManager/console.py
--- a/src/virtManager/console.py Tue Dec 21 20:21:23 2010 +0100
+++ b/src/virtManager/console.py Tue Dec 21 22:34:36 2010 +0100
@@ -1,6 +1,8 @@
+# -*- coding: utf-8 -*-
#
# Copyright (C) 2006-2008 Red Hat, Inc.
# Copyright (C) 2006 Daniel P. Berrange <berrange at redhat.com>
+# Copyright (C) 2010 Marc-André Lureau <marcandre.lureau at redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -46,6 +48,146 @@
return False
return True
+
+class Tunnel(object):
+ def __init__(self):
+ self.outfd = None
+ self.errfd = None
+ self.pid = None
+
+ def open(self, server, addr, port, username, sshport):
+ if self.outfd is not None:
+ return -1
+
+ # Build SSH cmd
+ argv = ["ssh", "ssh"]
+ if sshport:
+ argv += ["-p", str(sshport)]
+
+ if username:
+ argv += ['-l', username]
+
+ argv += [server]
+
+ # Build 'nc' command run on the remote host
+ #
+ # This ugly thing is a shell script to detect availability of
+ # the -q option for 'nc': debian and suse based distros need this
+ # flag to ensure the remote nc will exit on EOF, so it will go away
+ # when we close the VNC tunnel. If it doesn't go away, subsequent
+ # VNC connection attempts will hang.
+ #
+ # Fedora's 'nc' doesn't have this option, and apparently defaults
+ # to the desired behavior.
+ #
+ nc_params = "%s %s" % (addr, str(port))
+ nc_cmd = (
+ """nc -q 2>&1 | grep -q "requires an argument";"""
+ """if [ $? -eq 0 ] ; then"""
+ """ CMD="nc -q 0 %(nc_params)s";"""
+ """else"""
+ """ CMD="nc %(nc_params)s";"""
+ """fi;"""
+ """eval "$CMD";""" %
+ {'nc_params': nc_params})
+
+ argv.append("sh -c")
+ argv.append("'%s'" % nc_cmd)
+
+ argv_str = reduce(lambda x, y: x + " " + y, argv[1:])
+ logging.debug("Creating SSH tunnel: %s" % argv_str)
+
+ fds = socket.socketpair()
+ errorfds = socket.socketpair()
+
+ pid = os.fork()
+ if pid == 0:
+ fds[0].close()
+ errorfds[0].close()
+
+ os.close(0)
+ os.close(1)
+ os.close(2)
+ os.dup(fds[1].fileno())
+ os.dup(fds[1].fileno())
+ os.dup(errorfds[1].fileno())
+ os.execlp(*argv)
+ os._exit(1)
+ else:
+ fds[1].close()
+ errorfds[1].close()
+
+ logging.debug("Tunnel PID=%d OUTFD=%d ERRFD=%d" %
+ (pid, fds[0].fileno(), errorfds[0].fileno()))
+ errorfds[0].setblocking(0)
+
+ self.outfd = fds[0]
+ self.errfd = errorfds[0]
+ self.pid = pid
+
+ fd = fds[0].fileno()
+ if fd < 0:
+ raise SystemError("can't open a new tunnel: fd=%d" % fd)
+ return fd
+
+ def close(self):
+ if self.outfd is None:
+ return
+
+ logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
+ (self.pid, self.outfd.fileno(),
+ self.errfd.fileno()))
+ self.outfd.close()
+ self.outfd = None
+ self.errfd.close()
+ self.errfd = None
+
+ os.kill(self.pid, signal.SIGKILL)
+ self.pid = None
+
+ def get_err_output(self):
+ errout = ""
+ while True:
+ try:
+ new = self.errfd.recv(1024)
+ except:
+ break
+
+ if not new:
+ break
+
+ errout += new
+
+ return errout
+
+
+class Tunnels(object):
+ def __init__(self, server, addr, port, username, sshport):
+ self.server = server
+ self.addr = addr
+ self.port = port
+ self.username = username
+ self.sshport = sshport
+ self._tunnels = []
+
+ def open_new(self):
+ t = Tunnel()
+ fd = t.open(self.server, self.addr, self.port,
+ self.username, self.sshport)
+ self._tunnels.append(t)
+ return fd
+
+ def close_all(self):
+ for l in self._tunnels:
+ l.close()
+
+ def get_err_output(self):
+ errout = ""
+ for l in self._tunnels:
+ errout += l.get_err_output()
+ return errout
+
+
class vmmConsolePages(vmmGObjectUI):
def __init__(self, vm, window):
vmmGObjectUI.__init__(self, None, None)
@@ -70,7 +212,7 @@
# Initialize display widget
self.scale_type = self.vm.get_console_scaling()
- self.vncTunnel = None
+ self.tunnels = None
self.vncViewerRetriesScheduled = 0
self.vncViewerRetryDelay = 125
self.vnc_connected = False
@@ -428,9 +570,10 @@
def _vnc_disconnected(self, src_ignore):
errout = ""
- if self.vncTunnel is not None:
- errout = self.get_tunnel_err_output()
- self.close_tunnel()
+ if self.tunnels is not None:
+ errout = self.tunnels.get_err_output()
+ self.tunnels.close_all()
+ self.tunnels = None
self.vnc_connected = False
logging.debug("VNC disconnected")
@@ -474,104 +617,6 @@
if self.vncViewerRetryDelay < 2000:
self.vncViewerRetryDelay = self.vncViewerRetryDelay * 2
- def open_tunnel(self, server, vncaddr, vncport, username, sshport):
- if self.vncTunnel is not None:
- return -1
-
- # Build SSH cmd
- argv = ["ssh", "ssh"]
- if sshport:
- argv += ["-p", str(sshport)]
-
- if username:
- argv += ['-l', username]
-
- argv += [server]
-
- # Build 'nc' command run on the remote host
- #
- # This ugly thing is a shell script to detect availability of
- # the -q option for 'nc': debian and suse based distros need this
- # flag to ensure the remote nc will exit on EOF, so it will go away
- # when we close the VNC tunnel. If it doesn't go away, subsequent
- # VNC connection attempts will hang.
- #
- # Fedora's 'nc' doesn't have this option, and apparently defaults
- # to the desired behavior.
- #
- nc_params = "%s %s" % (vncaddr, str(vncport))
- nc_cmd = (
- """nc -q 2>&1 | grep -q "requires an argument";"""
- """if [ $? -eq 0 ] ; then"""
- """ CMD="nc -q 0 %(nc_params)s";"""
- """else"""
- """ CMD="nc %(nc_params)s";"""
- """fi;"""
- """eval "$CMD";""" %
- {'nc_params': nc_params})
-
- argv.append("sh -c")
- argv.append("'%s'" % nc_cmd)
-
- argv_str = reduce(lambda x, y: x + " " + y, argv[1:])
- logging.debug("Creating SSH tunnel: %s" % argv_str)
-
- fds = socket.socketpair()
- errorfds = socket.socketpair()
-
- pid = os.fork()
- if pid == 0:
- fds[0].close()
- errorfds[0].close()
-
- os.close(0)
- os.close(1)
- os.close(2)
- os.dup(fds[1].fileno())
- os.dup(fds[1].fileno())
- os.dup(errorfds[1].fileno())
- os.execlp(*argv)
- os._exit(1)
- else:
- fds[1].close()
- errorfds[1].close()
-
- logging.debug("Tunnel PID=%d OUTFD=%d ERRFD=%d" %
- (pid, fds[0].fileno(), errorfds[0].fileno()))
- errorfds[0].setblocking(0)
- self.vncTunnel = [fds[0], errorfds[0], pid]
-
- return fds[0].fileno()
-
- def close_tunnel(self):
- if self.vncTunnel is None:
- return
-
- logging.debug("Shutting down tunnel PID=%d OUTFD=%d ERRFD=%d" %
- (self.vncTunnel[2], self.vncTunnel[0].fileno(),
- self.vncTunnel[1].fileno()))
- self.vncTunnel[0].close()
- self.vncTunnel[1].close()
-
- os.kill(self.vncTunnel[2], signal.SIGKILL)
- self.vncTunnel = None
-
- def get_tunnel_err_output(self):
- errfd = self.vncTunnel[1]
- errout = ""
- while True:
- try:
- new = errfd.recv(1024)
- except:
- break
-
- if not new:
- break
-
- errout += new
-
- return errout
-
def skip_connect_attempt(self):
return (self.vnc_connected or
not self.is_visible())
@@ -628,12 +673,13 @@
try:
if trans in ("ssh", "ext"):
- if self.vncTunnel:
+ if self.tunnels:
# Tunnel already open, no need to continue
return
- fd = self.open_tunnel(connhost, "127.0.0.1", vncport,
- username, connport)
+ self.tunnels = Tunnels(connhost, "127.0.0.1", vncport,
+ username, connport)
+ fd = self.tunnels.open_new()
if fd >= 0:
self.vncViewer.open_fd(fd)
More information about the virt-tools-list
mailing list