mirror of
				https://github.com/retspen/webvirtcloud
				synced 2025-07-31 12:41:08 +00:00 
			
		
		
		
	replace tunnel.py with sshtunnel.py some small fixes
This commit is contained in:
		
							parent
							
								
									b6cb81c3bc
								
							
						
					
					
						commit
						0b86e34203
					
				
					 5 changed files with 14265 additions and 158 deletions
				
			
		|  | @ -15,16 +15,12 @@ if ROOT_PATH not in sys.path: | |||
| 
 | ||||
| django.setup() | ||||
| 
 | ||||
| # VENV_PATH = ROOT_PATH + '/venv/lib/python3.6/site-packages' | ||||
| # if VENV_PATH not in sys.path: | ||||
| #     sys.path.append(VENV_PATH) | ||||
| 
 | ||||
| import re | ||||
| import socket | ||||
| from six.moves import http_cookies as Cookie | ||||
| from webvirtcloud.settings import WS_PUBLIC_PORT, WS_HOST, WS_CERT | ||||
| from vrtManager.connection import CONN_SSH, CONN_SOCKET | ||||
| from console.tunnel import Tunnel | ||||
| from console.sshtunnels import SSHTunnels | ||||
| from optparse import OptionParser | ||||
| 
 | ||||
| parser = OptionParser() | ||||
|  | @ -119,7 +115,8 @@ def get_connection_infos(token): | |||
|         console_port = conn.get_console_port() | ||||
|         console_socket = conn.get_console_socket() | ||||
|     except Exception as e: | ||||
|         logging.error('Fail to retrieve console connection infos for token %s : %s' % (token, e)) | ||||
|         logging.error( | ||||
|             'Fail to retrieve console connection infos for token %s : %s' % (token, e)) | ||||
|         raise | ||||
|     return (connhost, connport, connuser, conntype, console_host, | ||||
|             console_port, console_socket) | ||||
|  | @ -131,6 +128,7 @@ class CompatibilityMixIn(object): | |||
|         # from the request to a cookie header, we should check | ||||
|         # also for this behavior | ||||
|         hcookie = self.headers.get('cookie') | ||||
| 
 | ||||
|         if hcookie: | ||||
|             cookie = Cookie.SimpleCookie() | ||||
|             for hcookie_part in hcookie.split(';'): | ||||
|  | @ -145,6 +143,7 @@ class CompatibilityMixIn(object): | |||
|                     if 'token' in cookie: | ||||
|                         token = cookie['token'].value | ||||
| 
 | ||||
| 
 | ||||
|         (connhost, connport, connuser, conntype, console_host, console_port, | ||||
|          console_socket) = get_connection_infos(token) | ||||
| 
 | ||||
|  | @ -176,10 +175,11 @@ class CompatibilityMixIn(object): | |||
|                 error_msg = "Try to open tunnel on %s@%s:%s on console %s:%s " | ||||
|                 error_msg += "(or socket %s)" | ||||
|                 self.msg(error_msg % (connuser, connhost, connport, | ||||
|                          console_host, console_port, console_socket)) | ||||
|                 tunnel = Tunnel() | ||||
|                 fd = tunnel.open(connhost, connuser, connport, | ||||
|                                       console_host, console_port, console_socket)) | ||||
|                 tunnel = SSHTunnels(connhost, connuser, connport, | ||||
|                                  console_host, console_port, console_socket) | ||||
|                 fd = tunnel.open_new() | ||||
|                 tunnel.unlock() | ||||
|                 tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) | ||||
|             except Exception as e: | ||||
|                 self.msg("Fail to open tunnel : %s" % e) | ||||
|  | @ -198,18 +198,17 @@ class CompatibilityMixIn(object): | |||
|         try: | ||||
|             self.msg("Start proxying") | ||||
|             self.do_proxy(tsock) | ||||
|         except: | ||||
|         except Exception: | ||||
|             if tunnel: | ||||
|                 self.vmsg( | ||||
|                     "%s:%s (via %s@%s:%s) : Target closed" % | ||||
|                     "%s:%s (via %s@%s:%s) : Websocket client or Target closed" % | ||||
|                     (console_host, console_port, connuser, connhost, connport)) | ||||
|                 if tsock: | ||||
|                     tsock.shutdown(socket.SHUT_RDWR) | ||||
|                     tsock.close() | ||||
|                 tunnel.close() | ||||
|                 tunnel.close_all() | ||||
|             raise | ||||
| 
 | ||||
| 
 | ||||
| if USE_HANDLER: | ||||
|     class NovaProxyRequestHandler(ProxyRequestHandler, CompatibilityMixIn): | ||||
|         def msg(self, *args, **kwargs): | ||||
|  |  | |||
							
								
								
									
										209
									
								
								console/sshtunnels.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								console/sshtunnels.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,209 @@ | |||
| # Copyright (C) 2014, 2015 Red Hat, Inc. | ||||
| # | ||||
| # This work is licensed under the GNU GPLv2 or later. | ||||
| # See the COPYING file in the top-level directory. | ||||
| 
 | ||||
| import functools | ||||
| import os | ||||
| import queue | ||||
| import socket | ||||
| import signal | ||||
| import threading | ||||
| 
 | ||||
| import logging as log | ||||
| 
 | ||||
| 
 | ||||
| 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 = None | ||||
|         self._queue = queue.Queue() | ||||
|         self._lock = threading.Lock() | ||||
| 
 | ||||
|     def _handle_queue(self): | ||||
|         while True: | ||||
|             lock_cb, cb, args, = self._queue.get() | ||||
|             lock_cb() | ||||
|             cb(*args) | ||||
| 
 | ||||
|     def schedule(self, lock_cb, cb, *args): | ||||
|         if not self._thread: | ||||
|             self._thread = threading.Thread(name="Tunnel thread", | ||||
|                                             target=self._handle_queue, | ||||
|                                             args=()) | ||||
|             self._thread.daemon = True | ||||
|         if not self._thread.is_alive(): | ||||
|             self._thread.start() | ||||
|         self._queue.put((lock_cb, cb, args)) | ||||
| 
 | ||||
|     def lock(self): | ||||
|         self._lock.acquire() | ||||
|     def unlock(self): | ||||
|         self._lock.release() | ||||
| 
 | ||||
| 
 | ||||
| _tunnel_scheduler = _TunnelScheduler() | ||||
| 
 | ||||
| 
 | ||||
| class _Tunnel(object): | ||||
|     def __init__(self): | ||||
|         self._pid = None | ||||
|         self._closed = False | ||||
|         self._errfd = None | ||||
| 
 | ||||
|     def close(self): | ||||
|         if self._closed: | ||||
|             return | ||||
|         self._closed = True | ||||
| 
 | ||||
|         log.debug("Close tunnel PID=%s ERRFD=%s", | ||||
|                       self._pid, self._errfd and self._errfd.fileno() or None) | ||||
| 
 | ||||
|         # Since this is a socket object, the file descriptor is closed | ||||
|         # when it's garbage collected. | ||||
|         self._errfd = 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 Exception: | ||||
|                 break | ||||
| 
 | ||||
|             if not new: | ||||
|                 break | ||||
| 
 | ||||
|             errout += new.decode() | ||||
| 
 | ||||
|         return errout | ||||
| 
 | ||||
|     def open(self, argv, sshfd): | ||||
|         if self._closed: | ||||
|             return | ||||
| 
 | ||||
|         errfds = socket.socketpair() | ||||
|         pid = os.fork() | ||||
|         if pid == 0: | ||||
|             errfds[0].close() | ||||
| 
 | ||||
|             os.dup2(sshfd.fileno(), 0) | ||||
|             os.dup2(sshfd.fileno(), 1) | ||||
|             os.dup2(errfds[1].fileno(), 2) | ||||
|             os.execlp(*argv) | ||||
|             os._exit(1)  # pylint: disable=protected-access | ||||
| 
 | ||||
|         sshfd.close() | ||||
|         errfds[1].close() | ||||
| 
 | ||||
|         self._errfd = errfds[0] | ||||
|         self._errfd.setblocking(0) | ||||
|         log.debug("Opened tunnel PID=%d ERRFD=%d", | ||||
|                       pid, self._errfd.fileno()) | ||||
| 
 | ||||
|         self._pid = pid | ||||
| 
 | ||||
| 
 | ||||
| def _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket): | ||||
|      | ||||
| 
 | ||||
|     # Build SSH cmd | ||||
|     argv = ["ssh", "ssh"] | ||||
|     if connport: | ||||
|         argv += ["-p", str(connport)] | ||||
| 
 | ||||
|     if connuser: | ||||
|         argv += ['-l', connuser] | ||||
| 
 | ||||
|     argv += [connhost] | ||||
| 
 | ||||
|     # 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 gsocket: | ||||
|         nc_params = "-U %s" % gsocket | ||||
|     else: | ||||
|         nc_params = "%s %s" % (gaddr, 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 = functools.reduce(lambda x, y: x + " " + y, argv[1:]) | ||||
|     log.debug("Pre-generated ssh command for info: %s", argv_str) | ||||
|     return argv | ||||
| 
 | ||||
| 
 | ||||
| class SSHTunnels(object): | ||||
|     def __init__(self, connhost, connuser, connport, gaddr, gport, gsocket): | ||||
|         self._tunnels = [] | ||||
|         self._sshcommand = _make_ssh_command(connhost, connuser, connport, gaddr, gport, gsocket) | ||||
|         self._locked = False | ||||
| 
 | ||||
|     def open_new(self): | ||||
|         t = _Tunnel() | ||||
|         self._tunnels.append(t) | ||||
| 
 | ||||
|         # socket FDs are closed when the object is garbage collected. This | ||||
|         # can close an FD behind spice/vnc's back which causes crashes. | ||||
|         # | ||||
|         # Dup a bare FD for the viewer side of things, but keep the high | ||||
|         # level socket object for the SSH side, since it simplifies things | ||||
|         # in that area. | ||||
|         viewerfd, sshfd = socket.socketpair() | ||||
|         _tunnel_scheduler.schedule(self._lock, t.open, self._sshcommand, sshfd) | ||||
| 
 | ||||
|         retfd = os.dup(viewerfd.fileno()) | ||||
|         log.debug("Generated tunnel fd=%s for viewer", retfd) | ||||
|         return retfd | ||||
| 
 | ||||
|     def close_all(self): | ||||
|         for l in self._tunnels: | ||||
|             l.close() | ||||
|         self._tunnels = [] | ||||
|         self.unlock() | ||||
| 
 | ||||
|     def get_err_output(self): | ||||
|         errstrings = [] | ||||
|         for l in self._tunnels: | ||||
|             e = l.get_err_output().strip() | ||||
|             if e and e not in errstrings: | ||||
|                 errstrings.append(e) | ||||
|         return "\n".join(errstrings) | ||||
| 
 | ||||
|     def _lock(self): | ||||
|         _tunnel_scheduler.lock() | ||||
|         self._locked = True | ||||
| 
 | ||||
|     def unlock(self, *args, **kwargs): | ||||
|         if self._locked: | ||||
|             _tunnel_scheduler.unlock(*args, **kwargs) | ||||
|             self._locked = False | ||||
|  | @ -111,7 +111,7 @@ | |||
|         } | ||||
| 
 | ||||
|         // uri = scheme + host + ":" + port; | ||||
|         uri = scheme + "{{ ws_host }}:{{ ws_port }}{{ ws_path }}"; | ||||
|         uri = scheme + "{{ ws_host }}:{{ ws_port }}"; | ||||
| 
 | ||||
|         if (path) { | ||||
|             uri += path[0] == '/' ? path : ('/' + path); | ||||
|  |  | |||
|  | @ -1,144 +0,0 @@ | |||
| # | ||||
| # This class provide from VirtManager project, from console.py | ||||
| # file. | ||||
| # | ||||
| # Copyright (C) 2006-2008 Red Hat, Inc. | ||||
| # Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com> | ||||
| # Copyright (C) 2010 Marc-André Lureau <marcandre.lureau@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 | ||||
| # 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 os | ||||
| import socket | ||||
| import signal | ||||
| import logging | ||||
| from functools import reduce | ||||
| 
 | ||||
| class Tunnel(object): | ||||
|     def __init__(self): | ||||
|         self.outfd = None | ||||
|         self.errfd = None | ||||
|         self.pid = None | ||||
| 
 | ||||
|     def open(self, connhost, connuser, connport, gaddr, gport, gsocket): | ||||
|         if self.outfd is not None: | ||||
|             return -1 | ||||
| 
 | ||||
|         # Build SSH cmd | ||||
|         argv = ["ssh", "ssh"] | ||||
|         if connport: | ||||
|             argv += ["-p", str(connport)] | ||||
| 
 | ||||
|         if connuser: | ||||
|             argv += ['-l', connuser] | ||||
| 
 | ||||
|         argv += [connhost] | ||||
| 
 | ||||
|         # 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 gsocket: | ||||
|             nc_params = "-U %s" % gsocket | ||||
|         else: | ||||
|             nc_params = "%s %s" % (gaddr, 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) | ||||
| 
 | ||||
|         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.dup2(fds[1].fileno(), 0) | ||||
|             os.dup2(fds[1].fileno(), 1) | ||||
|             os.dup2(errorfds[1].fileno(), 2) | ||||
|             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(False) | ||||
| 
 | ||||
|         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 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue