2012-12-17 8 views
5

Twisted conch SSH接続を閉じる正しい方法は何ですか?これを行う明示的な方法はありますか?Twisted conch SSH接続を閉じる正しい方法は何ですか?

私が見たTwisted conchの例では、すべてSSHチャネルを閉じてリアクタを停止しています。原子炉の停止は、接続を閉じるのを処理するようです。しかし、私はwxPythonでwxreactorを使用していますが、私は原子炉を停止したくありませんが、私が終了したらssh接続を終了したいと思います。

tc.c.s.connectionを見た後、serviceStopped()メソッドが移動するように思えました。チャネルが閉じられた後、私はまだサーバーからデータを取得していますように

Unhandled Error 
Traceback (most recent call last): 
    File "C:\Users\me\venv\lib\site-packages\twisted\internet\tcp.py", line 203, in doRead 
    return self._dataReceived(data) 
    File "C:\Users\me\venv\lib\site-packages\twisted\internet\tcp.py", line 209, in _dataReceived 
    rval = self.protocol.dataReceived(data) 
    File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\transport.py", line 438, in dataReceived 
    self.dispatchMessage(messageNum, packet[1:]) 
    File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\transport.py", line 460, in dispatchMessage 
    messageNum, payload) 
--- <exception caught here> --- 
    File "C:\Users\me\venv\lib\site-packages\twisted\python\log.py", line 84, in callWithLogger 
    return callWithContext({"system": lp}, func, *args, **kw) 
    File "C:\Users\me\venv\lib\site-packages\twisted\python\log.py", line 69, in callWithContext 
    return context.call({ILogContext: newCtx}, func, *args, **kw) 
    File "C:\Users\me\venv\lib\site-packages\twisted\python\context.py", line 118, in callWithContext 
    return self.currentContext().callWithContext(ctx, func, *args, **kw) 
    File "C:\Users\me\venv\lib\site-packages\twisted\python\context.py", line 81, in callWithContext 
    return func(*args,**kw) 
    File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\service.py", line 44, in packetReceived 
    return f(packet) 
    File "C:\Users\me\venv\lib\site-packages\twisted\conch\ssh\connection.py", line 228, in ssh_CHANNEL_DATA 
    channel = self.channels[localChannel] 
exceptions.KeyError: 0 

はルックス:それは終わったが、その後、私は以下のような例外を取得し始めたときに)開いているすべてのチャンネルを閉じ、(_cleanupGlobalDeferredsを実行します。 #twistedの誰かが、私がserviceStopped()を呼び出すべきではないと思ったようでした。なぜなら、それはTwistedの別の部分によって自動的に呼び出されるべきだからです。

私はTwistedソースコードでいくつかのことをしていましたが、serviceStoppedはt.c.s.t.SSHClientTransport.connectionLost()によって呼び出されるはずです。

私は自分のSF​​TPクライアントオブジェクトを追跡し、そのトランスポート属性を使ってSSH接続にアクセスしています。次に、問題を示すためにローカルで実行できる例を示します。生のものはhereをフェッチすることができます。

from os.path import basename 
import sys 

from twisted.conch.client.connect import connect 
from twisted.conch.client.options import ConchOptions 
from twisted.internet.defer import Deferred 
from twisted.conch.ssh import channel, userauth 
from twisted.conch.ssh.common import NS 
from twisted.conch.ssh.connection import SSHConnection 
from twisted.conch.ssh.filetransfer import FXF_WRITE, FXF_CREAT, \ 
    FXF_TRUNC, FileTransferClient 
from twisted.internet import reactor, defer 
from twisted.python.log import startLogging 

ACTIVE_CLIENTS = {} 
USERNAME = 'user'   # change me! 
PASSWORD = 'password'  # change me! 
HOST = ('hostname', 22)  # change me! 
TEST_FILE_PATH = __file__ 
TEST_FILE_NAME = basename(__file__) 


def openSFTP(user, host): 
    conn = SFTPConnection() 
    options = ConchOptions() 
    options['host'], options['port'] = host 
    conn._sftp = Deferred() 
    auth = SimpleUserAuth(user, conn) 
    connect(options['host'], options['port'], options, verifyHostKey, auth) 
    return conn._sftp 


def verifyHostKey(ui, hostname, ip, key): 
    return defer.succeed(True) 


class SimpleUserAuth(userauth.SSHUserAuthClient): 
    def getPassword(self): 
     return defer.succeed(PASSWORD) 


class SFTPConnection(SSHConnection): 
    def serviceStarted(self): 
     self.openChannel(SFTPChannel()) 


class SFTPChannel(channel.SSHChannel): 
    name = 'session' 

    def channelOpen(self, ignoredData): 
     d = self.conn.sendRequest(self, 'subsystem', NS('sftp'), 
            wantReply=True) 
     d.addCallback(self._cbFTP) 
     d.addErrback(self.printErr) 

    def _cbFTP(self, ignore): 
     client = FileTransferClient() 
     client.makeConnection(self) 
     self.dataReceived = client.dataReceived 
     ACTIVE_CLIENTS.update({self.conn.transport.transport.addr: client}) 
     self.conn._sftp.callback(None) 

    def printErr(self, msg): 
     print msg 
     return msg 


@defer.inlineCallbacks 
def main(): 
    d = openSFTP(USERNAME, HOST) 
    _ = yield d 

    client = ACTIVE_CLIENTS[HOST] 
    d = client.openFile(TEST_FILE_NAME, FXF_WRITE | FXF_CREAT | FXF_TRUNC, {}) 
    df = yield d 

    sf = open(TEST_FILE_PATH, 'rb') 
    d = df.writeChunk(0, sf.read()) 
    _ = yield d 

    sf.close() 
    d = df.close() 
    _ = yield d 

    ACTIVE_CLIENTS[HOST].transport.loseConnection() 
    # loseConnection() call above causes the following log messages: 
    # [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] sending close 0 
    # [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] unhandled request for exit-status 
    # [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] remote close 
    # [SSHChannel session (0) on SSHService ssh-connection on SSHClientTransport,client] closed 
    # I can see the channel closed on the server side: 
    # sshd[4485]: debug1: session_exit_message: session 0 channel 0 pid 4486 
    # sshd[4485]: debug1: session_exit_message: release channel 0 
    # sshd[4485]: debug1: session_by_channel: session 0 channel 0 

    ACTIVE_CLIENTS[HOST].transport.conn.transport.loseConnection() 
    # loseConnection() call above does not close the SSH connection. 

    reactor.callLater(5, reactor.stop) 
    # Stopping the reactor closes the SSH connection and logs the following messages: 
    # [SSHClientTransport,client] connection lost 
    # [SSHClientTransport,client] Stopping factory <twisted.conch.client.direct.SSHClientFactory instance at 0x02E5AF30> 
    # [-] Main loop terminated. 
    # On the server side: 
    # sshd[4485]: Closing connection to xxx.xxx.xxx.xxx 


if __name__ == '__main__': 
    startLogging(sys.stdout) 
    reactor.callWhenRunning(main) 
    reactor.run() 

SSH接続を閉じるには、私はt.c.c.d.SSHClientTransport.sendDisconnect()を呼び出すACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection()を呼んでいます。ここでsendDisconnect()メソッドです:

def sendDisconnect(self, code, reason): 
    if self.factory.d is None: 
     return 
    d, self.factory.d = self.factory.d, None 
    transport.SSHClientTransport.sendDisconnect(self, code, reason) 
    d.errback(error.ConchError(reason, code)) 

self.factory.dは常にそれがt.c.s.t.SSHClientTransport.sendDisconnect()を呼び出すことなく、返すように、このメソッドが呼び出されNoneにいるようです。私はそれがもともとt.c.c.d.connectの遅延セットだと思っていますが、ある時点ではNoneに設定されています。

私は、SSH接続を閉じる正しい方法はSSHClientTransport.loseConnection()だと思っていますが、twistedが別のものになると思ったらself.factory.dをNoneに設定するのはなぜですか?

lostConnection()がSSH接続を閉じる正しい方法でない場合、誰かが正しい方向に私を向けることができますか?

答えて

4

twisted.conch.client.direct.SSHClientFactorytwisted.conch.client.direct.SSHClientTransportのようです。これらのクラスは、コマンドラインツールconchを実装するために使用されることを最も直接的に意図しています。これは、SSHクライアントを構築するのにかなり便利だということを意味しています。それはまさにconchだからです。

しかし、conchコマンドラインツールを実装するのではなく、「その他」を行うことにあまり注意を払っていないため、それらはあまり一般的ではありません。

より一般的に適用可能なSSHクライアント転送クラスはtwisted.conch.ssh.transport.SSHClientTransportです。このクラスには、コマンドラインツールconchの特定の動作を実装するための余分なロジックはありません。 SSHクライアントロジックを持っているだけです。たとえば、説明できないself.factory.dの内部には、sendDisconnectのチェックがありません。そのsendDisconnectの実装では、切断パケットを送信して接続を終了します。

1

同じ問題が発生します。 sendDisconnect()が親実装を呼び出さないというバグを私は確信しています。 SSHClientTransportloseConnection()を呼び出すと、私のためにTCP接続が閉じず、lsof -p PIDを使って見ることができます。この問題を解決するには、connect()メソッドを使用して、SSHClientTransportという独自の実装を挿入します。この問題は、次のコードで修正されています。

class SSHClientTransport(direct.SSHClientTransport): 
    ''' 
    Orignal sendDisconnect() is bugged. 
    ''' 

    def sendDisconnect(self, code, reason): 
     d, self.factory.d = self.factory.d, None 
     # call the sendDisconnect() on the base SSHTransport, 
     # not the imediate parent class 
     transport.SSHClientTransport.sendDisconnect(self, code, reason) 
     if d: 
      d.errback(error.ConchError(reason, code)) 
関連する問題