mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-26 10:03:54 +03:00
console: Break out tunnel handling to its own file
This commit is contained in:
parent
e1b646594c
commit
6a9aa6827b
@ -30,16 +30,13 @@ from gi.repository import SpiceClientGLib
|
|||||||
import libvirt
|
import libvirt
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import Queue
|
|
||||||
import signal
|
|
||||||
import socket
|
import socket
|
||||||
import threading
|
|
||||||
|
|
||||||
from .autodrawer import AutoDrawer
|
from .autodrawer import AutoDrawer
|
||||||
from .baseclass import vmmGObjectUI, vmmGObject
|
from .baseclass import vmmGObjectUI, vmmGObject
|
||||||
from .serialcon import vmmSerialConsole
|
|
||||||
from .details import DETAILS_PAGE_CONSOLE
|
from .details import DETAILS_PAGE_CONSOLE
|
||||||
|
from .serialcon import vmmSerialConsole
|
||||||
|
from .sshtunnels import ConnectionInfo, SSHTunnels
|
||||||
|
|
||||||
# Console pages
|
# Console pages
|
||||||
(CONSOLE_PAGE_UNAVAILABLE,
|
(CONSOLE_PAGE_UNAVAILABLE,
|
||||||
@ -48,7 +45,7 @@ from .details import DETAILS_PAGE_CONSOLE
|
|||||||
CONSOLE_PAGE_OFFSET) = range(4)
|
CONSOLE_PAGE_OFFSET) = range(4)
|
||||||
|
|
||||||
|
|
||||||
def has_property(obj, setting):
|
def _has_property(obj, setting):
|
||||||
try:
|
try:
|
||||||
obj.get_property(setting)
|
obj.get_property(setting)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@ -56,272 +53,9 @@ def has_property(obj, setting):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
##################################
|
||||||
class ConnectionInfo(object):
|
# VNC/Spice abstraction handling #
|
||||||
"""
|
##################################
|
||||||
Holds all the bits needed to make a connection to a graphical console
|
|
||||||
"""
|
|
||||||
def __init__(self, conn, gdev):
|
|
||||||
self.gtype = gdev.type
|
|
||||||
self.gport = gdev.port and str(gdev.port) or None
|
|
||||||
self.gsocket = gdev.socket
|
|
||||||
self.gaddr = gdev.listen or "127.0.0.1"
|
|
||||||
self.gtlsport = gdev.tlsPort or None
|
|
||||||
|
|
||||||
self.transport, self.connuser = conn.get_transport()
|
|
||||||
|
|
||||||
(self._connhost,
|
|
||||||
self._connport) = conn.get_backend().get_uri_host_port()
|
|
||||||
if self._connhost == "localhost":
|
|
||||||
self._connhost = "127.0.0.1"
|
|
||||||
|
|
||||||
def _is_listen_localhost(self, host=None):
|
|
||||||
return (host or self.gaddr) in ["127.0.0.1", "::1"]
|
|
||||||
|
|
||||||
def _is_listen_any(self):
|
|
||||||
return self.gaddr in ["0.0.0.0", "::"]
|
|
||||||
|
|
||||||
def need_tunnel(self):
|
|
||||||
if not self._is_listen_localhost():
|
|
||||||
return False
|
|
||||||
return self.transport in ["ssh", "ext"]
|
|
||||||
|
|
||||||
def is_bad_localhost(self):
|
|
||||||
"""
|
|
||||||
Return True if the guest is listening on localhost, but the libvirt
|
|
||||||
URI doesn't give us any way to tunnel the connection
|
|
||||||
"""
|
|
||||||
host = self.get_conn_host()[0]
|
|
||||||
if self.need_tunnel():
|
|
||||||
return False
|
|
||||||
return self.transport and self._is_listen_localhost(host)
|
|
||||||
|
|
||||||
def get_conn_host(self):
|
|
||||||
host = self._connhost
|
|
||||||
port = self._connport
|
|
||||||
tlsport = None
|
|
||||||
|
|
||||||
if not self.need_tunnel():
|
|
||||||
port = self.gport
|
|
||||||
tlsport = self.gtlsport
|
|
||||||
if not self._is_listen_any():
|
|
||||||
host = self.gaddr
|
|
||||||
|
|
||||||
return host, port, tlsport
|
|
||||||
|
|
||||||
def logstring(self):
|
|
||||||
return ("proto=%s trans=%s connhost=%s connuser=%s "
|
|
||||||
"connport=%s gaddr=%s gport=%s gtlsport=%s gsocket=%s" %
|
|
||||||
(self.gtype, self.transport, self._connhost, self.connuser,
|
|
||||||
self._connport, self.gaddr, self.gport, self.gtlsport,
|
|
||||||
self.gsocket))
|
|
||||||
def console_active(self):
|
|
||||||
if self.gsocket:
|
|
||||||
return True
|
|
||||||
if (self.gport in [None, -1] and self.gtlsport in [None, -1]):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class _TunnelScheduler(object):
|
|
||||||
"""
|
|
||||||
If the user is using Spice + SSH URI + no SSH keys, we need to
|
|
||||||
serialize connection opening otherwise ssh-askpass gets all angry.
|
|
||||||
This handles the locking and scheduling.
|
|
||||||
|
|
||||||
It's only instantiated once for the whole app, because we serialize
|
|
||||||
independent of connection, vm, etc.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self._thread = threading.Thread(name="Tunnel thread",
|
|
||||||
target=self._handle_queue,
|
|
||||||
args=())
|
|
||||||
self._thread.daemon = True
|
|
||||||
self._queue = Queue.Queue()
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
def _handle_queue(self):
|
|
||||||
while True:
|
|
||||||
cb, args, = self._queue.get()
|
|
||||||
self.lock()
|
|
||||||
vmmGObject.idle_add(cb, *args)
|
|
||||||
|
|
||||||
def schedule(self, cb, *args):
|
|
||||||
if not self._thread.is_alive():
|
|
||||||
self._thread.start()
|
|
||||||
self._queue.put((cb, args))
|
|
||||||
|
|
||||||
def lock(self):
|
|
||||||
self._lock.acquire()
|
|
||||||
def unlock(self):
|
|
||||||
self._lock.release()
|
|
||||||
|
|
||||||
_tunnel_sched = _TunnelScheduler()
|
|
||||||
|
|
||||||
|
|
||||||
class _Tunnel(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.outfd = None
|
|
||||||
self.errfd = None
|
|
||||||
self.pid = None
|
|
||||||
self._outfds = None
|
|
||||||
self._errfds = None
|
|
||||||
self.closed = False
|
|
||||||
|
|
||||||
def open(self, ginfo):
|
|
||||||
self._outfds = socket.socketpair()
|
|
||||||
self._errfds = socket.socketpair()
|
|
||||||
|
|
||||||
return self._outfds[0].fileno(), self._launch_tunnel, ginfo
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.closed:
|
|
||||||
return
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
logging.debug("Close tunnel PID=%s OUTFD=%s ERRFD=%s",
|
|
||||||
self.pid,
|
|
||||||
self.outfd and self.outfd.fileno() or self._outfds,
|
|
||||||
self.errfd and self.errfd.fileno() or self._errfds)
|
|
||||||
|
|
||||||
if self.outfd:
|
|
||||||
self.outfd.close()
|
|
||||||
elif self._outfds:
|
|
||||||
self._outfds[0].close()
|
|
||||||
self._outfds[1].close()
|
|
||||||
self.outfd = None
|
|
||||||
self._outfds = None
|
|
||||||
|
|
||||||
if self.errfd:
|
|
||||||
self.errfd.close()
|
|
||||||
elif self._errfds:
|
|
||||||
self._errfds[0].close()
|
|
||||||
self._errfds[1].close()
|
|
||||||
self.errfd = None
|
|
||||||
self._errfds = None
|
|
||||||
|
|
||||||
if self.pid:
|
|
||||||
os.kill(self.pid, signal.SIGKILL)
|
|
||||||
os.waitpid(self.pid, 0)
|
|
||||||
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
|
|
||||||
|
|
||||||
def _launch_tunnel(self, ginfo):
|
|
||||||
if self.closed:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
host, port, ignore = ginfo.get_conn_host()
|
|
||||||
|
|
||||||
# Build SSH cmd
|
|
||||||
argv = ["ssh", "ssh"]
|
|
||||||
if port:
|
|
||||||
argv += ["-p", str(port)]
|
|
||||||
|
|
||||||
if ginfo.connuser:
|
|
||||||
argv += ['-l', ginfo.connuser]
|
|
||||||
|
|
||||||
argv += [host]
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
if ginfo.gsocket:
|
|
||||||
nc_params = "-U %s" % ginfo.gsocket
|
|
||||||
else:
|
|
||||||
nc_params = "%s %s" % (ginfo.gaddr, ginfo.gport)
|
|
||||||
|
|
||||||
nc_cmd = (
|
|
||||||
"""nc -q 2>&1 | grep "requires an argument" >/dev/null;"""
|
|
||||||
"""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)
|
|
||||||
|
|
||||||
pid = os.fork()
|
|
||||||
if pid == 0:
|
|
||||||
self._outfds[0].close()
|
|
||||||
self._errfds[0].close()
|
|
||||||
|
|
||||||
os.close(0)
|
|
||||||
os.close(1)
|
|
||||||
os.close(2)
|
|
||||||
os.dup(self._outfds[1].fileno())
|
|
||||||
os.dup(self._outfds[1].fileno())
|
|
||||||
os.dup(self._errfds[1].fileno())
|
|
||||||
os.execlp(*argv)
|
|
||||||
os._exit(1) # pylint: disable=protected-access
|
|
||||||
else:
|
|
||||||
self._outfds[1].close()
|
|
||||||
self._errfds[1].close()
|
|
||||||
|
|
||||||
logging.debug("Open tunnel PID=%d OUTFD=%d ERRFD=%d",
|
|
||||||
pid, self._outfds[0].fileno(), self._errfds[0].fileno())
|
|
||||||
self._errfds[0].setblocking(0)
|
|
||||||
|
|
||||||
self.outfd = self._outfds[0]
|
|
||||||
self.errfd = self._errfds[0]
|
|
||||||
self._outfds = None
|
|
||||||
self._errfds = None
|
|
||||||
self.pid = pid
|
|
||||||
|
|
||||||
|
|
||||||
class Tunnels(object):
|
|
||||||
def __init__(self, ginfo):
|
|
||||||
self.ginfo = ginfo
|
|
||||||
self._tunnels = []
|
|
||||||
|
|
||||||
def open_new(self):
|
|
||||||
t = _Tunnel()
|
|
||||||
fd, cb, args = t.open(self.ginfo)
|
|
||||||
self._tunnels.append(t)
|
|
||||||
_tunnel_sched.schedule(cb, args)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
lock = _tunnel_sched.lock
|
|
||||||
unlock = _tunnel_sched.unlock
|
|
||||||
|
|
||||||
|
|
||||||
class Viewer(vmmGObject):
|
class Viewer(vmmGObject):
|
||||||
def __init__(self, console):
|
def __init__(self, console):
|
||||||
@ -732,13 +466,13 @@ class SpiceViewer(Viewer):
|
|||||||
|
|
||||||
def get_desktop_resolution(self):
|
def get_desktop_resolution(self):
|
||||||
if (not self._display_channel or
|
if (not self._display_channel or
|
||||||
not has_property(self._display_channel, "width")):
|
not _has_property(self._display_channel, "width")):
|
||||||
return None
|
return None
|
||||||
return self._display_channel.get_properties("width", "height")
|
return self._display_channel.get_properties("width", "height")
|
||||||
|
|
||||||
def has_agent(self):
|
def has_agent(self):
|
||||||
if (not self.main_channel or
|
if (not self.main_channel or
|
||||||
not has_property(self.main_channel, "agent-connected")):
|
not _has_property(self.main_channel, "agent-connected")):
|
||||||
return False
|
return False
|
||||||
ret = self.main_channel.get_property("agent-connected")
|
ret = self.main_channel.get_property("agent-connected")
|
||||||
return ret
|
return ret
|
||||||
@ -791,12 +525,12 @@ class SpiceViewer(Viewer):
|
|||||||
self.spice_session.connect()
|
self.spice_session.connect()
|
||||||
|
|
||||||
def get_scaling(self):
|
def get_scaling(self):
|
||||||
if not has_property(self._display, "scaling"):
|
if not _has_property(self._display, "scaling"):
|
||||||
return False
|
return False
|
||||||
return self._display.get_property("scaling")
|
return self._display.get_property("scaling")
|
||||||
|
|
||||||
def set_scaling(self, scaling):
|
def set_scaling(self, scaling):
|
||||||
if not has_property(self._display, "scaling"):
|
if not _has_property(self._display, "scaling"):
|
||||||
logging.debug("Spice version doesn't support scaling.")
|
logging.debug("Spice version doesn't support scaling.")
|
||||||
return
|
return
|
||||||
self._display.set_property("scaling", scaling)
|
self._display.set_property("scaling", scaling)
|
||||||
@ -851,6 +585,10 @@ class SpiceViewer(Viewer):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
#####################
|
||||||
|
# UI logic handling #
|
||||||
|
#####################
|
||||||
|
|
||||||
class vmmConsolePages(vmmGObjectUI):
|
class vmmConsolePages(vmmGObjectUI):
|
||||||
def __init__(self, vm, builder, topwin):
|
def __init__(self, vm, builder, topwin):
|
||||||
vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin)
|
vmmGObjectUI.__init__(self, None, None, builder=builder, topwin=topwin)
|
||||||
@ -1078,7 +816,7 @@ class vmmConsolePages(vmmGObjectUI):
|
|||||||
self.gtk_settings_accel = settings.get_property('gtk-menu-bar-accel')
|
self.gtk_settings_accel = settings.get_property('gtk-menu-bar-accel')
|
||||||
settings.set_property('gtk-menu-bar-accel', None)
|
settings.set_property('gtk-menu-bar-accel', None)
|
||||||
|
|
||||||
if has_property(settings, "gtk-enable-mnemonics"):
|
if _has_property(settings, "gtk-enable-mnemonics"):
|
||||||
self.gtk_settings_mnemonic = settings.get_property(
|
self.gtk_settings_mnemonic = settings.get_property(
|
||||||
"gtk-enable-mnemonics")
|
"gtk-enable-mnemonics")
|
||||||
settings.set_property("gtk-enable-mnemonics", False)
|
settings.set_property("gtk-enable-mnemonics", False)
|
||||||
@ -1520,7 +1258,7 @@ class vmmConsolePages(vmmGObjectUI):
|
|||||||
self.set_enable_accel()
|
self.set_enable_accel()
|
||||||
|
|
||||||
if ginfo.need_tunnel():
|
if ginfo.need_tunnel():
|
||||||
self.tunnels = Tunnels(ginfo)
|
self.tunnels = SSHTunnels(ginfo)
|
||||||
self.viewer.open_ginfo(ginfo)
|
self.viewer.open_ginfo(ginfo)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logging.exception("Error connection to graphical console")
|
logging.exception("Error connection to graphical console")
|
||||||
|
296
virtManager/sshtunnels.py
Normal file
296
virtManager/sshtunnels.py
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
# MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import Queue
|
||||||
|
import socket
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from .baseclass import vmmGObject
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionInfo(object):
|
||||||
|
"""
|
||||||
|
Holds all the bits needed to make a connection to a graphical console
|
||||||
|
"""
|
||||||
|
def __init__(self, conn, gdev):
|
||||||
|
self.gtype = gdev.type
|
||||||
|
self.gport = gdev.port and str(gdev.port) or None
|
||||||
|
self.gsocket = gdev.socket
|
||||||
|
self.gaddr = gdev.listen or "127.0.0.1"
|
||||||
|
self.gtlsport = gdev.tlsPort or None
|
||||||
|
|
||||||
|
self.transport, self.connuser = conn.get_transport()
|
||||||
|
|
||||||
|
(self._connhost,
|
||||||
|
self._connport) = conn.get_backend().get_uri_host_port()
|
||||||
|
if self._connhost == "localhost":
|
||||||
|
self._connhost = "127.0.0.1"
|
||||||
|
|
||||||
|
def _is_listen_localhost(self, host=None):
|
||||||
|
return (host or self.gaddr) in ["127.0.0.1", "::1"]
|
||||||
|
|
||||||
|
def _is_listen_any(self):
|
||||||
|
return self.gaddr in ["0.0.0.0", "::"]
|
||||||
|
|
||||||
|
def need_tunnel(self):
|
||||||
|
if not self._is_listen_localhost():
|
||||||
|
return False
|
||||||
|
return self.transport in ["ssh", "ext"]
|
||||||
|
|
||||||
|
def is_bad_localhost(self):
|
||||||
|
"""
|
||||||
|
Return True if the guest is listening on localhost, but the libvirt
|
||||||
|
URI doesn't give us any way to tunnel the connection
|
||||||
|
"""
|
||||||
|
host = self.get_conn_host()[0]
|
||||||
|
if self.need_tunnel():
|
||||||
|
return False
|
||||||
|
return self.transport and self._is_listen_localhost(host)
|
||||||
|
|
||||||
|
def get_conn_host(self):
|
||||||
|
host = self._connhost
|
||||||
|
port = self._connport
|
||||||
|
tlsport = None
|
||||||
|
|
||||||
|
if not self.need_tunnel():
|
||||||
|
port = self.gport
|
||||||
|
tlsport = self.gtlsport
|
||||||
|
if not self._is_listen_any():
|
||||||
|
host = self.gaddr
|
||||||
|
|
||||||
|
return host, port, tlsport
|
||||||
|
|
||||||
|
def logstring(self):
|
||||||
|
return ("proto=%s trans=%s connhost=%s connuser=%s "
|
||||||
|
"connport=%s gaddr=%s gport=%s gtlsport=%s gsocket=%s" %
|
||||||
|
(self.gtype, self.transport, self._connhost, self.connuser,
|
||||||
|
self._connport, self.gaddr, self.gport, self.gtlsport,
|
||||||
|
self.gsocket))
|
||||||
|
def console_active(self):
|
||||||
|
if self.gsocket:
|
||||||
|
return True
|
||||||
|
if (self.gport in [None, -1] and self.gtlsport in [None, -1]):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class _TunnelScheduler(object):
|
||||||
|
"""
|
||||||
|
If the user is using Spice + SSH URI + no SSH keys, we need to
|
||||||
|
serialize connection opening otherwise ssh-askpass gets all angry.
|
||||||
|
This handles the locking and scheduling.
|
||||||
|
|
||||||
|
It's only instantiated once for the whole app, because we serialize
|
||||||
|
independent of connection, vm, etc.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self._thread = threading.Thread(name="Tunnel thread",
|
||||||
|
target=self._handle_queue,
|
||||||
|
args=())
|
||||||
|
self._thread.daemon = True
|
||||||
|
self._queue = Queue.Queue()
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def _handle_queue(self):
|
||||||
|
while True:
|
||||||
|
cb, args, = self._queue.get()
|
||||||
|
self.lock()
|
||||||
|
vmmGObject.idle_add(cb, *args)
|
||||||
|
|
||||||
|
def schedule(self, cb, *args):
|
||||||
|
if not self._thread.is_alive():
|
||||||
|
self._thread.start()
|
||||||
|
self._queue.put((cb, args))
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
self._lock.acquire()
|
||||||
|
def unlock(self):
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
|
|
||||||
|
class _Tunnel(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.outfd = None
|
||||||
|
self.errfd = None
|
||||||
|
self.pid = None
|
||||||
|
self._outfds = None
|
||||||
|
self._errfds = None
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def open(self, ginfo):
|
||||||
|
self._outfds = socket.socketpair()
|
||||||
|
self._errfds = socket.socketpair()
|
||||||
|
|
||||||
|
return self._outfds[0].fileno(), self._launch_tunnel, ginfo
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
logging.debug("Close tunnel PID=%s OUTFD=%s ERRFD=%s",
|
||||||
|
self.pid,
|
||||||
|
self.outfd and self.outfd.fileno() or self._outfds,
|
||||||
|
self.errfd and self.errfd.fileno() or self._errfds)
|
||||||
|
|
||||||
|
if self.outfd:
|
||||||
|
self.outfd.close()
|
||||||
|
elif self._outfds:
|
||||||
|
self._outfds[0].close()
|
||||||
|
self._outfds[1].close()
|
||||||
|
self.outfd = None
|
||||||
|
self._outfds = None
|
||||||
|
|
||||||
|
if self.errfd:
|
||||||
|
self.errfd.close()
|
||||||
|
elif self._errfds:
|
||||||
|
self._errfds[0].close()
|
||||||
|
self._errfds[1].close()
|
||||||
|
self.errfd = None
|
||||||
|
self._errfds = None
|
||||||
|
|
||||||
|
if self.pid:
|
||||||
|
os.kill(self.pid, signal.SIGKILL)
|
||||||
|
os.waitpid(self.pid, 0)
|
||||||
|
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
|
||||||
|
|
||||||
|
def _launch_tunnel(self, ginfo):
|
||||||
|
if self.closed:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
host, port, ignore = ginfo.get_conn_host()
|
||||||
|
|
||||||
|
# Build SSH cmd
|
||||||
|
argv = ["ssh", "ssh"]
|
||||||
|
if port:
|
||||||
|
argv += ["-p", str(port)]
|
||||||
|
|
||||||
|
if ginfo.connuser:
|
||||||
|
argv += ['-l', ginfo.connuser]
|
||||||
|
|
||||||
|
argv += [host]
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
if ginfo.gsocket:
|
||||||
|
nc_params = "-U %s" % ginfo.gsocket
|
||||||
|
else:
|
||||||
|
nc_params = "%s %s" % (ginfo.gaddr, ginfo.gport)
|
||||||
|
|
||||||
|
nc_cmd = (
|
||||||
|
"""nc -q 2>&1 | grep "requires an argument" >/dev/null;"""
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
pid = os.fork()
|
||||||
|
if pid == 0:
|
||||||
|
self._outfds[0].close()
|
||||||
|
self._errfds[0].close()
|
||||||
|
|
||||||
|
os.close(0)
|
||||||
|
os.close(1)
|
||||||
|
os.close(2)
|
||||||
|
os.dup(self._outfds[1].fileno())
|
||||||
|
os.dup(self._outfds[1].fileno())
|
||||||
|
os.dup(self._errfds[1].fileno())
|
||||||
|
os.execlp(*argv)
|
||||||
|
os._exit(1) # pylint: disable=protected-access
|
||||||
|
else:
|
||||||
|
self._outfds[1].close()
|
||||||
|
self._errfds[1].close()
|
||||||
|
|
||||||
|
logging.debug("Open tunnel PID=%d OUTFD=%d ERRFD=%d",
|
||||||
|
pid, self._outfds[0].fileno(), self._errfds[0].fileno())
|
||||||
|
self._errfds[0].setblocking(0)
|
||||||
|
|
||||||
|
self.outfd = self._outfds[0]
|
||||||
|
self.errfd = self._errfds[0]
|
||||||
|
self._outfds = None
|
||||||
|
self._errfds = None
|
||||||
|
self.pid = pid
|
||||||
|
|
||||||
|
|
||||||
|
class SSHTunnels(object):
|
||||||
|
_tunnel_sched = _TunnelScheduler()
|
||||||
|
|
||||||
|
def __init__(self, ginfo):
|
||||||
|
self.ginfo = ginfo
|
||||||
|
self._tunnels = []
|
||||||
|
|
||||||
|
def open_new(self):
|
||||||
|
t = _Tunnel()
|
||||||
|
fd, cb, args = t.open(self.ginfo)
|
||||||
|
self._tunnels.append(t)
|
||||||
|
self._tunnel_sched.schedule(cb, args)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def lock(self, *args, **kwargs):
|
||||||
|
return self._tunnel_sched.lock(*args, **kwargs)
|
||||||
|
def unlock(self, *args, **kwargs):
|
||||||
|
return self._tunnel_sched.unlock(*args, **kwargs)
|
Loading…
x
Reference in New Issue
Block a user