私は、確立された ssh トンネルを介していくつかのリモートデータベースを頻繁に照会するための Python スクリプトに取り組んでいます。私はparamikoライブラリにかなり精通しているので、それが私の選択でした。paramiko を使用して重要な問題に対処したり、python を使用して ssh トンネルを開始、制御、およびシャットダウンしたりできるように、これを完全な Python のままにしておくことをお勧めします。
このトピックに関していくつかの関連する質問がありましたが、それらのほとんどは回答が不完全なようでした. 以下の私の解決策は、これまでに見つけた解決策をハックしたものです。
問題は次のとおりです。最初のトンネルを(別のスレッドで)非常に簡単に作成し、DB/python を実行できますが、トンネルを閉じようとすると、localhost はバインドされたローカル ポートを解放しません。以下に、ソースと、プロセスの各ステップに関連する netstat データを含めました。
#!/usr/bin/python
import select
import SocketServer
import sys
import paramiko
from threading import Thread
import time
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.chain_host, self.chain_port), self.request.getpeername())
except Exception, e:
print('Incoming request to %s:%d failed: %s' % (self.chain_host, self.chain_port, repr(e)))
return
if chan is None:
print('Incoming request to %s:%d was rejected by the SSH server.' % (self.chain_host, self.chain_port))
return
print('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(), 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()
print('Tunnel closed from %r' % (self.request.getpeername(),))
class DBTunnel():
def __init__(self,ip):
self.c = paramiko.SSHClient()
self.c.load_system_host_keys()
self.c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.c.connect(ip, username='someuser')
self.trans = self.c.get_transport()
def startTunnel(self):
class SubHandler(Handler):
chain_host = '127.0.0.1'
chain_port = 5432
ssh_transport = self.c.get_transport()
def ThreadTunnel():
global t
t = ForwardServer(('', 3333), SubHandler)
t.serve_forever()
Thread(target=ThreadTunnel).start()
def stopTunnel(self):
t.shutdown()
self.trans.close()
self.c.close()
私は stopTunnel() タイプのメソッドを使用することになりますが、コードが完全に正しいわけではないことに気付きましたが、トンネルを適切にシャットダウンして結果をテストするための実験です。
最初に DBTunnel オブジェクトの作成を呼び出して startTunnel() を呼び出すと、netstat から次の結果が得られます。
tcp4 0 0 *.3333 *.* LISTEN
tcp4 0 0 MYIP.36316 REMOTE_HOST.22 ESTABLISHED
tcp4 0 0 127.0.0.1.5432 *.* LISTEN
stopTunnel() を呼び出すか、DBTunnel オブジェクト自体を削除すると、Python をすべて終了するまでこの接続が残り、ガベージ コレクターであると想定されるものが処理されます。
tcp4 0 0 *.3333 *.* LISTEN
この開いているソケットが DBConnect オブジェクトとは無関係にぶら下がっている理由と、スクリプト内から適切に閉じる方法を理解するとよいでしょう。Python を完全に終了する前に、同じローカル ポートを使用して別の接続を別の IP にバインドしようとすると (time_wait は問題ではありません)、悪名高い bind err 48 アドレスが使用されます。前もって感謝します :)