2012-12-17 53 views
5

什么是关闭扭曲海螺SSH连接的正确方法?有没有明确的方法来做到这一点?什么是关闭双绞海螺SSH连接的正确方法?

我看到的所有Twisted海螺的例子都关闭了SSH通道,然后停止反应堆。反应堆停堆似乎处理关闭连接。然而,我使用wxPython的wxreactor,并且我不想停止反应堆,但是当我完成它时,我想关闭ssh连接。

查看t.c.s.connection后,它看起来像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应该由t.c.s.t.SSHClientTransport.connectionLost()调用。

我跟踪我的SFTP客户端对象并通过其传输属性访问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连接,我打电话ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection()这就要求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似乎永远是无时,调用该方法,因此返回不调用t.c.s.t.SSHClientTransport.sendDisconnect()。我认为它最初是t.c.c.d.connect中的延迟集,但是在某些时候它被设置为None。

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

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

回答

4

这听起来像你使用twisted.conch.client.direct.SSHClientFactorytwisted.conch.client.direct.SSHClientTransport。这些类最直接用于实现conch命令行工具。这意味着它们对于构建SSH客户端非常有用,因为这正是conch

但是,它们的用处也不如人们想象的那么有用,因为它们并不十分注意执行conch命令行工具的任何其他操作。

更一般适用的SSH客户端传输类是twisted.conch.ssh.transport.SSHClientTransport。该类没有任何额外的逻辑来实现conch命令行工具的某些特定行为。它只有SSH客户端逻辑。例如,它在sendDisconnect里面没有一个无法解释的self.factory.d检查 - 它的sendDisconnect实现只是发送一个断开包,然后关闭连接。

1

我遇到了同样的问题。我确信它的一个bug,sendDisconnect()不会调用父实现。在SSHClientTransport上调用loseConnection()不会关闭我的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)) 
相关问题