5

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

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

tcsconnection を調べたところ、serviceStopped() メソッドが適しているように見えました。開いているすべてのチャネルを閉じ、終了時に _cleanupGlobalDeferreds() を実行しますが、次のような例外が発生し始めました。

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

チャネルが閉じられた後もサーバーからデータを取得しているようです。#twisted の誰かが、Twisted の別の部分によって自動的に呼び出される必要があるため、serviceStopped() を自分で呼び出すべきではないと考えているようです。

Twisted のソース コードを調べてみたところ、serviceStopped は tcstSSHClientTransport.connectionLost() によって呼び出されることになっていることがわかりました。

SFTP クライアント オブジェクトを追跡し、トランスポート属性を介して SSH 接続にアクセスしています。問題を示すためにローカルで実行できる例を次に示します。生はここで取得できます。

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 接続を閉じるために、ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection()which calls を呼び出していますt.c.c.d.SSHClientTransport.sendDisconnect()。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 は常に None のように見えるため、tcstSSHClientTransport.sendDisconnect() を呼び出さずに戻ります。本来は tccdconnect の deferred セットだったと思いますが、ある時点で None に設定されています。

SSHClientTransport.loseConnection() が SSH 接続を閉じる正しい方法だと思いますが、ねじれが別のものであると予想しているのに、self.factory.d が None に設定されるのはなぜですか?

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

4

2 に答える 2

4

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

conchただし、コマンド ライン ツールの実装以外の「その他」のことにはあまり注意を払っていないため、一般的には想像以上に有用ではありません。

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

于 2013-01-09T21:06:34.160 に答える