2012-12-17 17 views
5

Jaki jest prawidłowy sposób zamknięcia połączenia konwergentnego konektora SSH? Czy istnieje wyraźny sposób na zrobienie tego?Jaki jest prawidłowy sposób zamknięcia połączenia konwergentnego konektora SSH?

Wszystkie skręcone przykłady konchy Widziałem blisko kanału SSH, a następnie zatrzymałem reaktor. Zamykanie reaktora wydaje się obsługiwać zamykanie połączenia. Jednak używam wxreactor z wxPython i nie chcę zatrzymywać reaktora, ale chcę zamknąć połączenie ssh, gdy skończę z nim.

Po obejrzeniu połączenia t.c.s. wydaje się, że metoda serviceStopped() była drogą. To zamyka wszystkie otwarte kanały i biegnie _cleanupGlobalDeferreds() po zakończeniu, ale potem zaczęła się wyjątki jak poniżej:

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 

Wygląda jakbym nadal pobieranie danych z serwera po kanał został zamknięty. Ktoś z #twisted wydawał się myśleć, że nie powinienem wywoływać serviceStopped() samemu, ponieważ powinien być wywoływany automatycznie przez inną część Twisted.

Zrobiłem kilka szturchanie w Twisted kod źródłowy i stwierdził, że serviceStopped ma być wywoływane przez t.c.s.t.SSHClientTransport.connectionLost().

Śledzę moje obiekty klienta SFTP i uzyskuję dostęp do połączenia SSH za pośrednictwem ich atrybutu transportu. Oto przykład, który możesz uruchomić lokalnie, aby zademonstrować problem. Surowiec można pobrać 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() 

Aby zamknąć połączenie SSH, dzwonię ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection() który wywołuje t.c.c.d.SSHClientTransport.sendDisconnect(). Oto metoda 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 wydaje się być zawsze None, gdy ta metoda nazywa się tak zwraca bez wzywania t.c.s.t.SSHClientTransport.sendDisconnect(). Myślę, że pierwotnie był to odroczony zestaw w t.c.c.d.connect, ale w pewnym momencie jest ustawiony na None.

Podejrzewam, że SSHClientTransport.loseConnection() jest poprawnym sposobem zamknięcia połączeń SSH, ale dlaczego self.factory.d ma wartość None, gdy twisted oczekuje, że będzie to coś innego?

Jeśli metoda loseConnection() nie jest poprawną metodą zamykania połączeń SSH, czy ktoś mógłby wskazać mi właściwy kierunek?

Odpowiedz

4

Wygląda na to, że używasz twisted.conch.client.direct.SSHClientFactory i twisted.conch.client.direct.SSHClientTransport. Klasy te są najbardziej bezpośrednio przeznaczone do użycia przy użyciu narzędzia wiersza poleceń o nazwie conch. Oznacza to, że są one dość użyteczne przy budowaniu klienta SSH, ponieważ to jest dokładnie to, co jest conch.

Są one jednak nieco mniej przydatne, niż można to sobie wyobrazić, ponieważ nie przywiązują dużej wagi do robienia czegoś "innego" niż implementacja narzędzia wiersza poleceń conch.

Bardziej ogólnie obowiązująca klasa transportu klienta SSH to twisted.conch.ssh.transport.SSHClientTransport. Ta klasa nie ma żadnej dodatkowej logiki do implementacji jakiegoś określonego zachowania narzędzia wiersza poleceń conch. Ma tylko logikę klienta SSH. Na przykład nie ma on niewyjaśnionego testu wewnątrz implementacji - wysyła tylko pakiet rozłączający, a następnie zamyka połączenie.

1

Wpadam na ten sam problem. Jestem przekonany, że to błąd, który sendDisconnect() nie wywołuje implementacji nadrzędnej. Wywołanie loseConnection() na SSHClientTransport nie zamyka dla mnie połączenia TCP, co widzę, używając lsof -p PID. Aby rozwiązać ten problem, używam własnej metody connect(), aby wprowadzić własną implementację SSHClientTransport. Problem został rozwiązany za pomocą następującego kodu:

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)) 
Powiązane problemy