5

关闭 Twisted conch SSH 连接的正确方法是什么?有没有明确的方法来做到这一点?

我见过的所有 Twisted conch 示例都会关闭 SSH 通道然后停止反应器。反应堆关闭似乎可以处理关闭连接。但是,我将 wxreactor 与 wxPython 一起使用,我不想停止反应器,但我想在完成后关闭 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 中的某个人似乎认为我不应该自己调用 serviceStopped(),因为它应该由 Twisted 的不同部分自动调用。

我在 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 中的一个延迟集,但在某些时候它被设置为 None。

我怀疑 SSHClientTransport.loseConnection() 是关闭 SSH 连接的正确方法,但是为什么 self.factory.d 设置为 None 当 twisted 期望它是别的东西时?

如果 lostConnection() 不是关闭 SSH 连接的正确方法,有人能指出我正确的方向吗?

4

2 回答 2

4

听起来您正在使用twisted.conch.client.direct.SSHClientFactoryand 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 回答
1

我遇到了同样的问题。我确信它是一个sendDisconnect()不调用父实现的错误。调用不会关闭我的 TCP 连接,我可以看到loseConnection()使用. 为了解决这个问题,我使用我自己的方法,注入我自己的. 使用以下代码解决了该问题:SSHClientTransportlsof -p PIDconnect()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))
于 2013-05-28T14:03:12.033 回答