public
Last active

ssh tunnel for Fabric

  • Download Gist
tunnel.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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
"""
Fabric tunneling utilities
by shn@glucose.jp
 
class ForwardServer and relates things are refere Robey Pointer's paramiko example.
(http://bazaar.launchpad.net/~robey/paramiko/trunk/annotate/head%3A/demos/forward.py)
 
usage::
 
with make_tunnel('user@192.168.0.2:10022') as t:
run('pwd')
 
"""
 
import select
import SocketServer
#import random
import threading
import hashlib
from fabric.api import env
from fabric.network import join_host_strings, normalize
from fabric.state import connections
 
__all__ = ['tunnel', 'make_tunnel']
VERBOSE = True
 
 
def tunnel():
KEY = 'tunnel_hoststring'
if hasattr(env, KEY):
return make_tunnel(getattr(env, KEY))
else:
return NullTunnel()
 
def make_tunnel(tunnel=None, remote=None, local_port=None):
if remote is None:
remote = env.host_string
username, hostname, port = normalize(remote)
if local_port is None:
#local_port = random.randint(10000, 65535)
local_port = port_from_host(remote)
client = connections[tunnel]
return TunnelThread(hostname, port, local_port, client.get_transport())
 
 
def port_from_host(hoststring):
return int(hashlib.sha1(hoststring).hexdigest()[-4:], 16) | 1024
 
 
class NullTunnel():
def __enter__(self):
env.tunnel = self
return self
 
def __exit__(self, *exc):
del env['tunnel']
pass
 
def rsync_shell_option(self):
#return ''
return env.steppingstone
 
 
class TunnelThread(threading.Thread):
def __init__(self, remote_host, remote_port, local_port, transport):
threading.Thread.__init__(self)
class SubHander (Handler):
chain_host = remote_host
chain_port = int(remote_port, 10)
ssh_transport = transport
self.local_port = local_port
self.server = ForwardServer(('127.0.0.1', self.local_port), SubHander)
def run(self):
self.server.serve_forever()
 
def __enter__(self):
self.old_host_string = env.host_string
env.host_string = join_host_strings(env.user, '127.0.0.1', self.local_port)
env.host = '127.0.0.1'
env.port = self.local_port
self.start()
env.tunnel = self
return self
 
def __exit__(self, *exc):
if env.host_string in connections:
connections[env.host_string].close()
del connections[env.host_string]
self.server.shutdown()
env.host_string = self.old_host_string
env.user, env.host, env.port = normalize(env.host_string)
del env['tunnel']
 
def rsync_shell_option(self):
return '-e "ssh -p %d -i %s"' % (self.local_port, env.key_filename)
 
 
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
 
 
class Handler(SocketServer.BaseRequestHandler):
def handle(self):
request_peername = self.request.getpeername()
try:
chan = self.ssh_transport.open_channel('direct-tcpip',
(self.chain_host, self.chain_port),
request_peername)
except Exception, e:
verbose('Incoming request to %s:%d failed: %s' % (self.chain_host,
self.chain_port,
repr(e)))
return
if chan is None:
verbose('Incoming request to %s:%d was rejected by the SSH server.' %
(self.chain_host, self.chain_port))
return
 
verbose('Connected! Tunnel open %r -> %r -> %r' % (request_peername,
chan.getpeername(), (self.chain_host, self.chain_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()
self.request.close()
verbose('Tunnel closed from %r' % (request_peername,))
 
 
def forward_tunnel(local_port, remote_host, remote_port, transport):
# this is a little convoluted, but lets me configure things for the Handler
# object. (SocketServer doesn't give Handlers any way to access the outer
# server normally.)
class SubHander (Handler):
chain_host = remote_host
chain_port = remote_port
ssh_transport = transport
ForwardServer(('', local_port), SubHander).serve_forever()
 
 
def verbose(s):
if VERBOSE:
print s

nice, how to do multi host, it seems the port will be closed after first host.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.