Bagaimana cara yang benar untuk menutup koneksi SSH Twisted conch?

Bagaimana cara yang benar untuk menutup koneksi SSH Twisted conch? Apakah ada cara eksplisit untuk melakukan ini?

Semua contoh Keong Twisted yang pernah saya lihat menutup saluran SSH lalu mematikan reaktor. Pematian reaktor tampaknya menangani penutupan sambungan. Namun, saya menggunakan wxreactor dengan wxPython dan saya tidak ingin menghentikan reaktor, tetapi saya ingin menutup koneksi ssh setelah saya selesai menggunakannya.

Setelah melihat t.c.s.connection sepertinya metode serviceStopped() adalah cara yang tepat. Itu menutup semua saluran terbuka dan menjalankan _cleanupGlobalDeferreds() setelah selesai, tetapi kemudian saya mulai mendapatkan pengecualian seperti di bawah ini:

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

Sepertinya saya masih mendapatkan data dari server setelah saluran ditutup. Seseorang di #twisted sepertinya berpikir saya tidak seharusnya memanggil serviceStopped() sendiri karena itu harus dipanggil secara otomatis oleh bagian lain dari Twisted.

Saya mencari-cari kode sumber Twisted dan menemukan bahwa serviceStopped seharusnya dipanggil oleh t.c.s.t.SSHClientTransport.connectionLost().

Saya melacak objek klien SFTP saya dan mengakses koneksi SSH melalui atribut transportnya. Berikut adalah contoh yang dapat Anda jalankan secara lokal untuk menunjukkan masalahnya. Bahan mentahnya dapat diambil di sini.

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

Untuk menutup koneksi SSH, saya menelepon ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection() yang memanggil t.c.c.d.SSHClientTransport.sendDisconnect(). Inilah metode 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 sepertinya selalu Tidak Ada saat metode ini dipanggil sehingga ia kembali tanpa memanggil t.c.s.t.SSHClientTransport.sendDisconnect(). Saya pikir ini awalnya merupakan set yang ditangguhkan di t.c.c.d.connect, tetapi pada titik tertentu diatur ke Tidak Ada.

Saya curiga SSHClientTransport.loseConnection() adalah cara yang benar untuk menutup koneksi SSH, tetapi mengapa self.factory.d disetel ke Tidak Ada ketika twisted mengharapkannya menjadi sesuatu yang lain?

Jika lostConnection() bukan cara yang benar untuk menutup koneksi SSH, bisakah seseorang mengarahkan saya ke arah yang benar?


person Vye    schedule 17.12.2012    source sumber


Jawaban (2)


Sepertinya Anda menggunakan twisted.conch.client.direct.SSHClientFactory dan twisted.conch.client.direct.SSHClientTransport. Kelas-kelas ini secara langsung dimaksudkan untuk digunakan untuk mengimplementasikan alat baris perintah conch. Ini berarti mereka cukup berguna untuk membangun klien SSH, karena itulah conch.

Namun, secara umum mereka juga kurang berguna daripada yang dibayangkan, karena mereka tidak terlalu memperhatikan melakukan apa pun ''selain'' selain mengimplementasikan alat baris perintah conch.

Kelas transport klien SSH yang lebih umum digunakan adalah twisted.conch.ssh.transport.SSHClientTransport. Kelas ini tidak memiliki logika tambahan untuk mengimplementasikan beberapa perilaku tertentu dari alat baris perintah conch. Itu hanya memiliki logika klien SSH. Misalnya, tidak ada tanda centang self.factory.d yang tidak dapat dijelaskan di dalam sendDisconnect - implementasi sendDisconnect-nya hanya mengirimkan paket pemutusan dan kemudian menutup koneksi.

person Jean-Paul Calderone    schedule 09.01.2013

Saya mengalami masalah yang sama. Saya yakin ini adalah bug yang sendDisconnect() tidak memanggil implementasi induk. Memanggil loseConnection() pada SSHClientTransport tidak menutup koneksi TCP untuk saya, yang dapat saya lihat menggunakan lsof -p PID. Untuk memperbaiki masalah ini saya menggunakan metode connect() saya sendiri, untuk menyuntikkan implementasi SSHClientTransport saya sendiri. Masalahnya diperbaiki dengan kode berikut:

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))
person Marek Kowalski    schedule 28.05.2013