Как правильно закрыть SSH-соединение 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() какие звонки 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-соединений, может кто-нибудь указать мне правильное направление?

2 ответа

Похоже, вы используете twisted.conch.client.direct.SSHClientFactory а также twisted.conch.client.direct.SSHClientTransport, Эти классы предназначены для непосредственного использования для реализации conch инструмент командной строки. Это означает, что они довольно полезны для построения клиента SSH, так как это именно то, что conch является.

Тем не менее, они также несколько менее полезны, чем можно себе представить, поскольку они не уделяют много внимания выполнению чего-либо "иного", чем реализация conch инструмент командной строки.

Более широко применимый класс транспорта клиента SSH: twisted.conch.ssh.transport.SSHClientTransport, Этот класс не имеет никакой дополнительной логики для реализации определенного поведения conch инструмент командной строки. У него просто есть клиентская логика SSH. Например, это не имеет необъяснимого self.factory.d проверить внутри sendDisconnect - его sendDisconnect Реализация просто отправляет пакет разъединения, а затем закрывает соединение.

Я сталкиваюсь с той же проблемой. Я убежден, что это ошибка sendDisconnect() не вызывает родительскую реализацию. призвание loseConnection() на SSHClientTransport не закрывает для меня 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))
Другие вопросы по тегам