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

Source Code for Module x2go.forward

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