title: What is the correct way to close a Twisted conch SSH connection?
body: <p>What is the correct way to close a Twisted conch SSH connection? Is there an explicit way to do this?</p>
  
  <p>All of the Twisted conch examples I have seen close the SSH channel then stop the reactor. The reactor shutdown seems to handle closing the connection. However, I'm using the wxreactor with wxPython and I do not want to stop the reactor, but I want to close the ssh connection when I'm finished with it.</p>
  
  <p>After looking at t.c.s.connection it seemed like the serviceStopped() method was the way to go. It closes all open channels and runs _cleanupGlobalDeferreds() when finished, but then I started getting exceptions like the one below:</p>
  
  <pre><code>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)
  --- &lt;exception caught here&gt; ---
    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
  </code></pre>
  
  <p>Looks like I'm still getting data from the server after the channel has been closed. Someone in #twisted seemed to think I shouldn't be calling serviceStopped() myself because it should be called automatically by a different part of Twisted.</p>
  
  <p>I did some poking around in the Twisted source code and found that serviceStopped is supposed to be called by t.c.s.t.SSHClientTransport.connectionLost().</p>
  
  <p>I'm keeping track of my SFTP client objects and accessing the SSH connection via their transport attribute. Here is an example you can run locally to demonstrate the issue. The raw can be fetched <a href="https://raw.github.com/Vye/troubleshooting/master/sftp.py">here</a>.</p>
  
  <pre><code>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 &lt;twisted.conch.client.direct.SSHClientFactory instance at 0x02E5AF30&gt;
      # [-] 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()
  </code></pre>
  
  <p>To close the SSH connection, I'm calling <code>ACTIVE_CLIENTS[HOST].transport.conn.transport(t.c.c.d.SSHClientTransport instance).loseConnection()</code> which calls <code>t.c.c.d.SSHClientTransport.sendDisconnect()</code>. Here's the sendDisconnect() method:</p>
  
  <pre><code>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))
  </code></pre>
  
  <p>self.factory.d seems to always be None when this method is called so it returns without calling t.c.s.t.SSHClientTransport.sendDisconnect(). I think it was originally a deferred set in t.c.c.d.connect, but at some point it is set to None.</p>
  
  <p>I suspect that SSHClientTransport.loseConnection() is the correct way to close SSH connections, but why is self.factory.d set to None when twisted expects it to be something else? </p>
  
  <p>If loseConnection() is not the correct way to close SSH connections could someone point me in the right direction?</p>