public
Last active

SSHForwarder class for use with the python ssh module: http://pypi.python.org/pypi/ssh

  • Download Gist
forwarder.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
#!/usr/bin/env python
 
# This code is licensed according to the main package that it depends on:
# http://pypi.python.org/pypi/ssh
# License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
# http://www.gnu.org/licenses/lgpl.html
 
import sys, os, os.path
import select
import SocketServer
import logging
import threading
from logging import info, debug, error
from types import ListType
 
import ssh
from ssh import SSHClient, SSHConfig, WarningPolicy, client
from ssh.client import SSH_PORT
 
class ForwardServer (SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
 
class Handler (SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel('direct-tcpip',
(self.target_host, self.target_port),
self.request.getpeername())
except Exception, e:
error('Incoming request to %s:%d failed: %s' %
(self.target_host, self.target_port, repr(e)))
return
if chan is None:
error('Incoming request to %s:%d was rejected by the SSH server.' %
(self.target_host, self.target_port))
return
 
info('Connected! Tunnel open %r -> %r -> %r' %
(self.request.getpeername(), chan.getpeername(),
(self.target_host, self.target_port)))
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
chan.close()
peer = self.request.getpeername()
self.request.close()
info('Tunnel closed from %r' % (peer,))
 
 
class SSHForwarder(SSHClient):
def __init__(self, proxy_host, port, target_host='localhost', user=None, config=None):
super(SSHForwarder, self).__init__()
if config:
self.config = config
else:
self.config = SSHConfig()
try:
f = open(os.path.join(os.environ['HOME'], ".ssh/config"))
self.config.parse(f)
except Exception:
sys.exc_clear()
hconfig = self.config.lookup(proxy_host)
user = hconfig['user'] if (hconfig and hconfig['user']) else os.environ['USER']
self.host = proxy_host
self.user = user
self.load_system_host_keys()
self.set_missing_host_key_policy(WarningPolicy)
 
info("Connecting to proxy host '{host}' as user '{user}'".format(**self.__dict__))
self.connect(self.host, SSH_PORT, username=self.user)
self.forwarders = {}
self.daemons = {}
for p in set(port if (type(port) is ListType) else [port]):
target = target_host
class SubHandler (Handler):
target_host = target
target_port = p
ssh_transport = self.get_transport()
 
self.forwarders[p] = ForwardServer(('', p), SubHandler)
self.daemons[p] = threading.Thread(target=self.forwarders[p].serve_forever)
self.daemons[p].daemon = True
forward_info = {'p':str(p), 'target':target, 'host':self.host}
info("Forwarding port '{p}' to target host '{target}' via proxy host '{host}'".format(**forward_info))
self.daemons[p].start()
 
def close(self):
try:
for f in self.forwarders.values():
f.shutdown()
f.server_close()
finally:
super(SSHForwarder, self).close()
 
def main():
try:
proxy_host = 'proxyhost'
target_port = 4000
target_host = 'targethost'
f = SSHForwarder(proxy_host, target_port, target_host)
# .... DO SOME STUFF ....
finally:
if f:
f.close()
if __name__ == "__main__":
main()

Thank you, very nice and useful class. What's the license of this code?

I updated the code for two things: 1) to clarify that the intended license is LGPL and 2) to update the code to use the python ssh module which was forked from the original paramiko library (which is now deprecated). I have not tested the updated code, but it should work. Let me know if you have any problems with it.

Thank you very much, I'll give it a try and report if it worked properly :metal:

Where are docstrings and documentation? It's hard to understand this as a beginner.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.