4

私は、確立された 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 アドレスが使用されます。前もって感謝します :)

4

4 に答える 4

1

SocketServer の shutdown メソッドがソケットを適切にシャットダウン/クローズしていないようです。コードに以下の変更を加えることで、SocketServer オブジェクトへのアクセスを維持し、ソケットに直接アクセスしてソケットを閉じます。私の場合は socket.close() が機能しますが、他のリソースがそのソケットにアクセスしている場合、他の人は socket.shutdown() に続いて socket.close() に興味があるかもしれません。

[参照: socket.shutdown と socket.close の比較

def ThreadTunnel():
    self.t = ForwardServer(('127.0.0.1', 3333), SubHandler)
    self.t.serve_forever()
Thread(target=ThreadTunnel).start()

def stopTunnel(self):
    self.t.shutdown()
    self.trans.close()
    self.c.close()
    self.t.socket.close()
于 2010-12-13T05:09:38.663 に答える
1

デモ コードに示されているように、Subhandler ハックを行う必要がないことに注意してください。コメントが間違っています。ハンドラーはサーバーのデータにアクセスできます。ハンドラー内で使用できますself.server.instance_data

ハンドラーで次のコードを使用する場合は、次を使用します

  • self.server.chain_host
  • self.server.chain_port
  • self.server.ssh_transport

class ForwardServer(SocketServer.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True

    def __init__(
          self, connection, handler, chain_host, chain_port, ssh_transport):
        SocketServer.ThreadingTCPServer.__init__(self, connection, handler)
        self.chain_host = chain_host
        self.chain_port = chain_port
        self.ssh_transport = ssh_transport
...

server = ForwardServer(('', local_port), Handler, 
                       remote_host, remote_port, transport)
server.serve_forever()
于 2012-02-15T23:46:02.160 に答える
0

準備が整う前にトンネルを使用しようとしないように、生成されたスレッドと呼び出し元の間に何らかの同期を追加することをお勧めします。何かのようなもの:

    from threading import Event   
    def startTunnel(self):
        class SubHandler(Handler):
            chain_host = '127.0.0.1'
            chain_port = 5432
            ssh_transport = self.c.get_transport()
        mysignal = Event()
        mysignal.clear()
        def ThreadTunnel():
            global t
            t = ForwardServer(('', 3333), SubHandler)
            mysignal.set() 
            t.serve_forever()
        Thread(target=ThreadTunnel).start()
        mysignal.wait()
于 2011-05-02T02:11:54.293 に答える