1#
2# This file is adapted from a paramiko demo, and thus licensed under LGPL 2.1.
3# Original Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
4# Edits Copyright (C) 2010 The IPython Team
5#
6# Paramiko is free software; you can redistribute it and/or modify it under the
7# terms of the GNU Lesser General Public License as published by the Free
8# Software Foundation; either version 2.1 of the License, or (at your option)
9# any later version.
10#
11# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
12# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
14# details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA.
19
20"""
21Sample script showing how to do local port forwarding over paramiko.
22
23This script connects to the requested SSH server and sets up local port
24forwarding (the openssh -L option) from a local port through a tunneled
25connection to a destination reachable from the SSH server machine.
26"""
27
28from __future__ import print_function
29
30import logging
31import select
32try:  # Python 3
33    import socketserver
34except ImportError:  # Python 2
35    import SocketServer as socketserver
36
37logger = logging.getLogger('ssh')
38
39class ForwardServer (socketserver.ThreadingTCPServer):
40    daemon_threads = True
41    allow_reuse_address = True
42
43
44class Handler (socketserver.BaseRequestHandler):
45
46    def handle(self):
47        try:
48            chan = self.ssh_transport.open_channel('direct-tcpip',
49                                                   (self.chain_host, self.chain_port),
50                                                   self.request.getpeername())
51        except Exception as e:
52            logger.debug('Incoming request to %s:%d failed: %s' % (self.chain_host,
53                                                              self.chain_port,
54                                                              repr(e)))
55            return
56        if chan is None:
57            logger.debug('Incoming request to %s:%d was rejected by the SSH server.' %
58                    (self.chain_host, self.chain_port))
59            return
60
61        logger.debug('Connected!  Tunnel open %r -> %r -> %r' % (self.request.getpeername(),
62                                                            chan.getpeername(), (self.chain_host, self.chain_port)))
63        while True:
64            r, w, x = select.select([self.request, chan], [], [])
65            if self.request in r:
66                data = self.request.recv(1024)
67                if len(data) == 0:
68                    break
69                chan.send(data)
70            if chan in r:
71                data = chan.recv(1024)
72                if len(data) == 0:
73                    break
74                self.request.send(data)
75        chan.close()
76        self.request.close()
77        logger.debug('Tunnel closed ')
78
79
80def forward_tunnel(local_port, remote_host, remote_port, transport):
81    # this is a little convoluted, but lets me configure things for the Handler
82    # object.  (SocketServer doesn't give Handlers any way to access the outer
83    # server normally.)
84    class SubHander (Handler):
85        chain_host = remote_host
86        chain_port = remote_port
87        ssh_transport = transport
88    ForwardServer(('127.0.0.1', local_port), SubHander).serve_forever()
89
90
91__all__ = ['forward_tunnel']
92