Package x2go :: Module forward
[frames] | no frames]

Source Code for Module x2go.forward

  1  #!/usr/bin/env python 
  2   
  3  # Copyright (C) 2010-2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2Go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU Affero General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2Go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU Affero General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Affero General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  Python Gevent based port forwarding server (openssh -L option) for the 
 22  proxying of graphical X2Go elements. 
 23   
 24  """ 
 25  __NAME__ = "x2gofwtunnel-pylib" 
 26   
 27  # modules 
 28  import copy 
 29   
 30  # gevent/greenlet 
 31  import gevent 
 32  from gevent import select, socket 
 33  from gevent.server import StreamServer 
 34   
 35  # Python X2Go modules 
 36  import log 
 37  from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS 
 38   
39 -class X2goFwServer(StreamServer):
40 """\ 41 L{X2goFwServer} implements a gevent's StreamServer based Paramiko/SSH port 42 forwarding server. 43 44 An L{X2goFwServer} class object is used to tunnel graphical trafic 45 through an external proxy command launched by a C{X2goProxy*} backend. 46 47 """
48 - def __init__ (self, listener, remote_host, remote_port, ssh_transport, session_instance=None, session_name=None, logger=None, loglevel=log.loglevel_DEFAULT,):
49 """\ 50 @param listener: listen on TCP/IP socket C{(<IP>, <Port>)} 51 @type listener: C{tuple} 52 @param remote_host: hostname or IP of remote host (in case of X2Go mostly 127.0.0.1) 53 @type remote_host: C{str} 54 @param remote_port: port of remote host 55 @type remote_port: C{int} 56 @param ssh_transport: a valid Paramiko/SSH transport object 57 @type ssh_transport: C{obj} 58 @param session_instance: the complete L{X2goSession} instance of the X2Go session this port forwarding server belongs to. 59 Note: for new L{X2goSession} instances the object has the session name not yet set(!!!) 60 @type session_instance: C{obj} 61 @param session_name: the session name of the X2Go session this port forwarding server belongs to 62 @type session_name: C{str} 63 @param logger: you can pass an L{X2goLogger} object to the 64 L{X2goFwServer} constructor 65 @type logger: C{obj} 66 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 67 constructed with the given loglevel 68 @type loglevel: C{int} 69 70 """ 71 if logger is None: 72 self.logger = log.X2goLogger(loglevel=loglevel) 73 else: 74 self.logger = copy.deepcopy(logger) 75 self.logger.tag = __NAME__ 76 77 self.chan = None 78 self.is_active = False 79 self.failed = False 80 self.keepalive = False 81 self.chain_host = remote_host 82 self.chain_port = remote_port 83 self.ssh_transport = ssh_transport 84 self.session_name = session_name 85 self.session_instance = session_instance 86 87 self.fw_socket = None 88 89 StreamServer.__init__(self, listener, self.x2go_forward_tunnel_handle)
90
91 - def x2go_forward_tunnel_handle(self, fw_socket, address):
92 """\ 93 Handle for SSH/Paramiko forwarding tunnel. 94 95 @param fw_socket: local end of the forwarding tunnel 96 @type fw_socket: C{obj} 97 @param address: unused/ignored 98 @type address: C{tuple} 99 100 """ 101 self.fw_socket = fw_socket 102 103 # disable Nagle algorithm in TCP/IP protocol 104 self.fw_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 105 106 _success = False 107 _count = 0 108 _maxwait = 20 109 110 while not _success and _count < _maxwait: 111 112 # it is recommended here to have passed on the session instance to this object... 113 if self.session_instance: 114 if not self.session_instance.is_connected(): 115 break 116 117 _count += 1 118 try: 119 self.chan = self.ssh_transport.open_channel('direct-tcpip', 120 (self.chain_host, self.chain_port), 121 self.fw_socket.getpeername()) 122 chan_peername = self.chan.getpeername() 123 _success = True 124 except Exception, e: 125 self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, 126 self.chain_port, 127 _count, 128 _maxwait, 129 repr(e)), 130 loglevel=log.loglevel_WARN) 131 gevent.sleep(.4) 132 133 if not _success: 134 self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, 135 self.chain_port, 136 _count), 137 loglevel=log.loglevel_ERROR) 138 if self.session_instance: 139 self.session_instance.set_session_name(self.session_name) 140 self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port) 141 self.failed = True 142 143 else: 144 145 self.logger('connected! Tunnel open %r -> %r -> %r' % (self.fw_socket.getpeername(), 146 chan_peername, (self.chain_host, self.chain_port)), 147 loglevel=log.loglevel_INFO) 148 149 # once we are here, we can presume the tunnel to be active... 150 self.is_active = True 151 152 self.keepalive = True 153 try: 154 while self.keepalive: 155 r, w, x = select.select([self.fw_socket, self.chan], [], []) 156 if fw_socket in r: 157 data = fw_socket.recv(1024) 158 if len(data) == 0: 159 break 160 self.chan.send(data) 161 if self.chan in r: 162 data = self.chan.recv(1024) 163 if len(data) == 0: 164 break 165 fw_socket.send(data) 166 self.close_channel() 167 self.close_socket() 168 except socket.error: 169 pass 170 171 self.is_active = False 172 self.logger('Tunnel closed from %r' % (chan_peername,), 173 loglevel=log.loglevel_INFO)
174
175 - def close_channel(self):
176 """\ 177 Close an open channel again. 178 179 """ 180 #if self.chan is not None and _X2GOCLIENT_OS != "Windows": 181 if self.chan is not None: 182 try: 183 if _X2GOCLIENT_OS != 'Windows': 184 self.chan.close() 185 self.chan = None 186 except EOFError: 187 pass
188
189 - def close_socket(self):
190 """\ 191 Close the forwarding tunnel's socket again. 192 193 """ 194 _success = False 195 _count = 0 196 _maxwait = 20 197 198 # try at least <_maxwait> times 199 while not _success and _count < _maxwait: 200 _count += 1 201 try: 202 self.close_channel() 203 if self.fw_socket is not None: 204 self.fw_socket.close() 205 _success = True 206 except socket.error: 207 gevent.sleep(.2) 208 self.logger('could not close fw_tunnel socket, try again (%s of %s)' % (_count, _maxwait), loglevel=log.loglevel_WARN) 209 210 if _count >= _maxwait: 211 self.logger('forwarding tunnel to [%s]:%d could not be closed properly' % (self.chain_host, self.chain_port), loglevel=log.loglevel_WARN)
212
213 - def stop(self):
214 """\ 215 Stop the forwarding tunnel. 216 217 """ 218 self.close_socket() 219 StreamServer.stop(self)
220 221
222 -def start_forward_tunnel(local_host='127.0.0.1', local_port=22022, 223 remote_host='127.0.0.1', remote_port=22, 224 ssh_transport=None, 225 session_instance=None, 226 session_name=None, 227 logger=None, ):
228 """\ 229 Setup up a Paramiko/SSH port forwarding tunnel (like openssh -L option). 230 231 The tunnel is used to transport X2Go graphics data through a proxy application like nxproxy. 232 233 @param local_host: local starting point of the forwarding tunnel 234 @type local_host: C{int} 235 @param local_port: listen port of the local starting point 236 @type local_port: C{int} 237 @param remote_host: from the endpoint of the tunnel, connect to host C{<remote_host>}... 238 @type remote_host: C{str} 239 @param remote_port: ... on port C{<remote_port>} 240 @type remote_port: C{int} 241 @param ssh_transport: the Paramiko/SSH transport (i.e. the X2Go session's Paramiko/SSH transport object) 242 @type ssh_transport: C{obj} 243 @param session_instance: the L{X2goSession} instance that initiates this tunnel 244 @type session_instance: C{obj} 245 @param session_name: the session name of the X2Go session this port forwarding server belongs to 246 @type session_name: C{str} 247 @param logger: an X2goLogger object 248 @type logger: C{obj} 249 250 @return: returns an L{X2goFwServer} instance 251 @rtype: C{obj} 252 253 """ 254 fw_server = X2goFwServer(listener=(local_host, local_port), 255 remote_host=remote_host, remote_port=remote_port, 256 ssh_transport=ssh_transport, 257 session_instance=session_instance, session_name=session_name, 258 logger=logger, 259 ) 260 try: 261 fw_server.start() 262 except socket.error: 263 fw_server.failed = True 264 fw_server.is_active = False 265 pass 266 267 return fw_server
268
269 -def stop_forward_tunnel(fw_server):
270 """\ 271 Tear down a given Paramiko/SSH port forwarding tunnel. 272 273 @param fw_server: an L{X2goFwServer} instance as returned by the L{start_forward_tunnel()} function 274 @type fw_server: C{obj} 275 276 """ 277 if fw_server is not None: 278 fw_server.keepalive = False 279 gevent.sleep(.5) 280 fw_server.stop()
281 282 283 if __name__ == '__main__': 284 pass 285