2012-12-17 6 views
5

Was ist der richtige Weg, um eine Twisted Conch SSH Verbindung zu schließen? Gibt es einen expliziten Weg, dies zu tun?Was ist der richtige Weg, um eine Twisted Conch SSH Verbindung zu schließen?

Alle Twisted-Conch-Beispiele, die ich gesehen habe, schließen den SSH-Kanal und stoppen dann den Reaktor. Die Reaktorabschaltung scheint die Verbindung zu schließen. Allerdings verwende ich den wxreactor mit wxPython und ich möchte den Reaktor nicht stoppen, aber ich möchte die ssh-Verbindung schließen, wenn ich damit fertig bin.

Nach dem Blick auf tc.cs.connection schien es wie die ServiceStopped() -Methode war der Weg zu gehen. Es schließt alle geöffneten Kanäle und läuft _cleanupGlobalDeferreds(), wenn Sie fertig, aber dann begann ich Ausnahmen wie das immer unter:

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 

Sieht aus wie ich vom Server noch Daten bin immer, nachdem der Kanal geschlossen wurde. Jemand in #twisted schien zu denken, dass ich nicht selbst serviceStopped() aufrufen sollte, weil es automatisch von einem anderen Teil von Twisted aufgerufen werden sollte.

Ich stocherte im Twisted-Quellcode herum und stellte fest, dass serviceStopped von t.c.s.SSHClientTransport.connectionLost() aufgerufen werden sollte.

Ich verfolge meine SFTP-Client-Objekte und greife über ihr Transportattribut auf die SSH-Verbindung zu. Hier ist ein Beispiel, das Sie lokal ausführen können, um das Problem zu demonstrieren. Das Rohteil kann geholt werden 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() 

die SSH-Verbindung zu schließen, ich ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection() ruft die t.c.c.d.SSHClientTransport.sendDisconnect() nennt. Hier ist die sendDisconnect() -Methode:

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 Keine immer zu sein scheint, wenn diese Methode aufgerufen wird, so kehrt ohne t.c.s.t.SSHClientTransport.sendDisconnect() aufrufen. Ich denke, es war ursprünglich eine verzögerte Menge in tc.c.c.connect, aber irgendwann wird es auf None gesetzt.

Ich vermute, dass SSHClientTransport.loseConnection() der richtige Weg ist, SSH-Verbindungen zu schließen, aber warum ist self.factory.d auf None gesetzt, wenn Twist erwartet, dass es etwas anderes ist?

Wenn loseConnection() nicht der richtige Weg ist, um SSH-Verbindungen zu schließen, könnte mir jemand in die richtige Richtung zeigen?

Antwort

4

Es klingt, als ob Sie twisted.conch.client.direct.SSHClientFactory und twisted.conch.client.direct.SSHClientTransport verwenden. Diese Klassen sind am direktesten dazu bestimmt, das Befehlszeilentool conch zu implementieren. Dies bedeutet, dass sie ziemlich nützlich für die Erstellung eines SSH-Clients sind, da genau das conch ist.

Sie sind aber auch etwas weniger allgemein nützlich, als man sich vorstellen kann, da sie nicht viel Aufmerksamkeit darauf verwenden, irgendetwas anderes zu tun als die Implementierung des Befehlszeilenprogramms conch.

Die allgemeinere SSH-Client-Transportklasse ist twisted.conch.ssh.transport.SSHClientTransport. Diese Klasse verfügt über keine zusätzliche Logik zum Implementieren eines bestimmten Verhaltens des Befehlszeilentools conch. Es hat nur SSH-Client-Logik. Zum Beispiel hat es keinen unerklärten self.factory.d Check innerhalb sendDisconnect - seine sendDisconnect Implementierung sendet nur ein Disconnect-Paket und schließt dann die Verbindung.

1

Ich laufe auf das gleiche Problem. Ich bin davon überzeugt, es ist ein Fehler, sendDisconnect() ruft nicht übergeordnete Implementierung. Der Aufruf von loseConnection() auf SSHClientTransport schließt die TCP-Verbindung für mich nicht, die ich mit lsof -p PID sehen kann. Um dieses Problem zu beheben, verwende ich meine eigene connect() Methode, um meine eigene Implementierung von SSHClientTransport zu injizieren. Das Problem wurde mit folgendem Code behoben:

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))