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

Source Code for Module x2go.forward

  1  #!/usr/bin/env python 
  2   
  3  # Copyright (C) 2010-2011 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 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 General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU 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 os, sys, 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  import x2go_exceptions 
 39   
40 -class X2goFwServer(StreamServer):
41 """\ 42 L{X2goFwServer} implements a gevent's StreamServer based Paramiko/SSH port 43 forwarding server. 44 45 An L{X2goFwServer} class object is used to tunnel graphical trafic 46 through an external proxy command launched by a C{X2goProxy*} backend. 47 48 """
49 - def __init__ (self, listener, remote_host, remote_port, ssh_transport, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT,):
50 """\ 51 @param listener: listen on TCP/IP socket C{(<IP>, <Port>)} 52 @type listener: C{tuple} 53 @param remote_host: hostname or IP of remote host (in case of X2go mostly 127.0.0.1) 54 @type remote_host: C{str} 55 @param remote_port: port of remote host 56 @type remote_port: C{int} 57 @param ssh_transport: a valid Paramiko/SSH transport object 58 @type ssh_transport: C{instance} 59 @param logger: you can pass an L{X2goLogger} object to the 60 L{X2goFwServer} constructor 61 @type logger: C{instance} 62 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 63 constructed with the given loglevel 64 @type loglevel: C{int} 65 66 """ 67 if logger is None: 68 self.logger = log.X2goLogger(loglevel=loglevel) 69 else: 70 self.logger = copy.deepcopy(logger) 71 self.logger.tag = __NAME__ 72 73 self.chan = None 74 self.is_active = False 75 self.keepalive = False 76 self.chain_host = remote_host 77 self.chain_port = remote_port 78 self.ssh_transport = ssh_transport 79 self.session_instance = session_instance 80 81 self.fw_socket = None 82 83 StreamServer.__init__(self, listener, self.x2go_forward_tunnel_handle)
84
85 - def x2go_forward_tunnel_handle(self, fw_socket, address):
86 """\ 87 Handle for SSH/Paramiko forwarding tunnel. 88 89 @param fw_socket: local end of the forwarding tunnel 90 @type fw_socket: C{instance} 91 @param address: unused/ignored 92 @type address: C{tuple} 93 94 """ 95 self.fw_socket = fw_socket 96 97 _success = False 98 _count = 0 99 _maxwait = 20 100 101 while not _success and _count < _maxwait: 102 103 # it is recommended here to have passed on the session instance to this object... 104 if self.session_instance: 105 if not self.session_instance.is_connected(): 106 break 107 108 _count += 1 109 try: 110 self.chan = self.ssh_transport.open_channel('direct-tcpip', 111 (self.chain_host, self.chain_port), 112 self.fw_socket.getpeername()) 113 chan_peername = self.chan.getpeername() 114 _success = True 115 except Exception, e: 116 self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, 117 self.chain_port, 118 _count, 119 _maxwait, 120 repr(e)), 121 loglevel=log.loglevel_WARN) 122 gevent.sleep(.4) 123 124 125 if not _success: 126 self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, 127 self.chain_port, 128 _count), 129 loglevel=log.loglevel_ERROR) 130 else: 131 # once we are here, we can presume the tunnel to be active... 132 self.is_active = True 133 134 if self.chan is None: 135 self.logger('incoming request to [%s]:%d was rejected by the SSH server.' % 136 (self.chain_host, self.chain_port), loglevel=log.loglevel_ERROR) 137 if self.session_instance: 138 self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port) 139 return 140 else: 141 self.logger('connected! Tunnel open %r -> %r -> %r' % (self.fw_socket.getpeername(), 142 chan_peername, (self.chain_host, self.chain_port)), 143 loglevel=log.loglevel_INFO) 144 self.keepalive = True 145 try: 146 while self.keepalive: 147 r, w, x = select.select([self.fw_socket, self.chan], [], []) 148 if fw_socket in r: 149 data = fw_socket.recv(1024) 150 if len(data) == 0: 151 break 152 self.chan.send(data) 153 if self.chan in r: 154 data = self.chan.recv(1024) 155 if len(data) == 0: 156 break 157 fw_socket.send(data) 158 self.close_channel() 159 self.close_socket() 160 except socket.error: 161 pass 162 163 self.is_active = False 164 self.logger('Tunnel closed from %r' % (chan_peername,), 165 loglevel=log.loglevel_INFO)
166
167 - def close_channel(self):
168 """\ 169 Close an open channel again. 170 171 """ 172 #if self.chan is not None and _X2GOCLIENT_OS != "Windows": 173 if self.chan is not None: 174 try: 175 if _X2GOCLIENT_OS != 'Windows': 176 self.chan.close() 177 self.chan = None 178 except EOFError: 179 pass
180
181 - def close_socket(self):
182 """\ 183 Close the forwarding tunnel's socket again. 184 185 """ 186 _success = False 187 _count = 0 188 _maxwait = 20 189 190 # try at least <_maxwait> times 191 while not _success and _count < _maxwait: 192 _count += 1 193 try: 194 self.close_channel() 195 if self.fw_socket is not None: 196 self.fw_socket.close() 197 _success = True 198 except socket.error: 199 gevent.sleep(.2) 200 self.logger('could not close fw_tunnel socket, try again (%s of %s)' % (_count, _maxwait), loglevel=log.loglevel_WARN) 201 202 if _count >= _maxwait: 203 self.logger('forwarding tunnel to [%s]:%d could not be closed properly' % (self.chain_host, self.chain_port), loglevel=log.loglevel_WARN)
204
205 - def stop(self):
206 """\ 207 Stop the forwarding tunnel. 208 209 """ 210 self.close_socket() 211 StreamServer.stop(self)
212 213
214 -def start_forward_tunnel(local_host='127.0.0.1', local_port=22022, 215 remote_host='127.0.0.1', remote_port=22, 216 ssh_transport=None, 217 session_instance=None, 218 logger=None, ):
219 """\ 220 Setup up a Paramiko/SSH port forwarding tunnel (like openssh -L option). 221 222 The tunnel is used to transport X2go graphics data through a proxy application like nxproxy. 223 224 @param local_host: local starting point of the forwarding tunnel 225 @type local_host: C{int} 226 @param local_port: listen port of the local starting point 227 @type local_port: C{int} 228 @param remote_host: from the endpoint of the tunnel, connect to host C{<remote_host>}... 229 @type remote_host: C{str} 230 @param remote_port: ... on port C{<remote_port>} 231 @type remote_port: C{int} 232 @param ssh_transport: the Paramiko/SSH transport (i.e. the X2go sessions Paramiko/SSH transport object) 233 @type ssh_transport: C{instance} 234 @param session_instance: the L{X2goSession} instance that initiates this tunnel 235 @type session_instance: C{instance} 236 @param logger: an X2goLogger object 237 @type logger: C{instance} 238 239 @return: returns an L{X2goFwServer} instance 240 @rtype: C{instance} 241 242 """ 243 try: 244 fw_server = X2goFwServer(listener=(local_host, local_port), 245 remote_host=remote_host, remote_port=remote_port, 246 ssh_transport=ssh_transport, session_instance=session_instance, 247 logger=logger, 248 ) 249 try: 250 fw_server.start() 251 return fw_server 252 except socket.error: 253 pass 254 except x2go_exceptions.X2goFwTunnelException: 255 pass 256 257 return None
258 259
260 -def stop_forward_tunnel(fw_server):
261 """\ 262 Tear down a given Paramiko/SSH port forwarding tunnel. 263 264 @param fw_server: an L{X2goFwServer} instance as returned by the L{start_forward_tunnel()} function 265 @type fw_server: C{instance} 266 267 """ 268 if fw_server is not None: 269 fw_server.keepalive = False 270 gevent.sleep(.5) 271 fw_server.stop()
272 273 274 if __name__ == '__main__': 275 pass 276