1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
28 import os, sys, copy
29
30
31 import gevent
32 from gevent import select, socket
33 from gevent.server import StreamServer
34
35
36 import log
37 from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
38 import x2go_exceptions
39
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
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
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
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
168 """\
169 Close an open channel again.
170
171 """
172
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
182 """\
183 Close the forwarding tunnel's socket again.
184
185 """
186 _success = False
187 _count = 0
188 _maxwait = 20
189
190
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
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
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