forwarding.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. This module contains the implementation of the TCP forwarding, which allows
  5. clients and servers to forward arbitrary TCP data across the connection.
  6. Maintainer: Paul Swartz
  7. """
  8. from __future__ import division, absolute_import
  9. import struct
  10. from twisted.internet import protocol, reactor
  11. from twisted.internet.endpoints import HostnameEndpoint, connectProtocol
  12. from twisted.python import log
  13. from twisted.python.compat import _PY3, unicode
  14. from twisted.conch.ssh import common, channel
  15. class SSHListenForwardingFactory(protocol.Factory):
  16. def __init__(self, connection, hostport, klass):
  17. self.conn = connection
  18. self.hostport = hostport # tuple
  19. self.klass = klass
  20. def buildProtocol(self, addr):
  21. channel = self.klass(conn = self.conn)
  22. client = SSHForwardingClient(channel)
  23. channel.client = client
  24. addrTuple = (addr.host, addr.port)
  25. channelOpenData = packOpen_direct_tcpip(self.hostport, addrTuple)
  26. self.conn.openChannel(channel, channelOpenData)
  27. return client
  28. class SSHListenForwardingChannel(channel.SSHChannel):
  29. def channelOpen(self, specificData):
  30. log.msg('opened forwarding channel %s' % self.id)
  31. if len(self.client.buf)>1:
  32. b = self.client.buf[1:]
  33. self.write(b)
  34. self.client.buf = b''
  35. def openFailed(self, reason):
  36. self.closed()
  37. def dataReceived(self, data):
  38. self.client.transport.write(data)
  39. def eofReceived(self):
  40. self.client.transport.loseConnection()
  41. def closed(self):
  42. if hasattr(self, 'client'):
  43. log.msg('closing local forwarding channel %s' % self.id)
  44. self.client.transport.loseConnection()
  45. del self.client
  46. class SSHListenClientForwardingChannel(SSHListenForwardingChannel):
  47. name = b'direct-tcpip'
  48. class SSHListenServerForwardingChannel(SSHListenForwardingChannel):
  49. name = b'forwarded-tcpip'
  50. class SSHConnectForwardingChannel(channel.SSHChannel):
  51. """
  52. Channel used for handling server side forwarding request.
  53. It acts as a client for the remote forwarding destination.
  54. @ivar hostport: C{(host, port)} requested by client as forwarding
  55. destination.
  56. @type hostport: L{tuple} or a C{sequence}
  57. @ivar client: Protocol connected to the forwarding destination.
  58. @type client: L{protocol.Protocol}
  59. @ivar clientBuf: Data received while forwarding channel is not yet
  60. connected.
  61. @type clientBuf: L{bytes}
  62. @var _reactor: Reactor used for TCP connections.
  63. @type _reactor: A reactor.
  64. @ivar _channelOpenDeferred: Deferred used in testing to check the
  65. result of C{channelOpen}.
  66. @type _channelOpenDeferred: L{twisted.internet.defer.Deferred}
  67. """
  68. _reactor = reactor
  69. def __init__(self, hostport, *args, **kw):
  70. channel.SSHChannel.__init__(self, *args, **kw)
  71. self.hostport = hostport
  72. self.client = None
  73. self.clientBuf = b''
  74. def channelOpen(self, specificData):
  75. """
  76. See: L{channel.SSHChannel}
  77. """
  78. log.msg("connecting to %s:%i" % self.hostport)
  79. ep = HostnameEndpoint(
  80. self._reactor, self.hostport[0], self.hostport[1])
  81. d = connectProtocol(ep, SSHForwardingClient(self))
  82. d.addCallbacks(self._setClient, self._close)
  83. self._channelOpenDeferred = d
  84. def _setClient(self, client):
  85. """
  86. Called when the connection was established to the forwarding
  87. destination.
  88. @param client: Client protocol connected to the forwarding destination.
  89. @type client: L{protocol.Protocol}
  90. """
  91. self.client = client
  92. log.msg("connected to %s:%i" % self.hostport)
  93. if self.clientBuf:
  94. self.client.transport.write(self.clientBuf)
  95. self.clientBuf = None
  96. if self.client.buf[1:]:
  97. self.write(self.client.buf[1:])
  98. self.client.buf = b''
  99. def _close(self, reason):
  100. """
  101. Called when failed to connect to the forwarding destination.
  102. @param reason: Reason why connection failed.
  103. @type reason: L{twisted.python.failure.Failure}
  104. """
  105. log.msg("failed to connect: %s" % reason)
  106. self.loseConnection()
  107. def dataReceived(self, data):
  108. """
  109. See: L{channel.SSHChannel}
  110. """
  111. if self.client:
  112. self.client.transport.write(data)
  113. else:
  114. self.clientBuf += data
  115. def closed(self):
  116. """
  117. See: L{channel.SSHChannel}
  118. """
  119. if self.client:
  120. log.msg('closed remote forwarding channel %s' % self.id)
  121. if self.client.channel:
  122. self.loseConnection()
  123. self.client.transport.loseConnection()
  124. del self.client
  125. def openConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
  126. remoteHP, origHP = unpackOpen_direct_tcpip(data)
  127. return SSHConnectForwardingChannel(remoteHP,
  128. remoteWindow=remoteWindow,
  129. remoteMaxPacket=remoteMaxPacket,
  130. avatar=avatar)
  131. class SSHForwardingClient(protocol.Protocol):
  132. def __init__(self, channel):
  133. self.channel = channel
  134. self.buf = b'\000'
  135. def dataReceived(self, data):
  136. if self.buf:
  137. self.buf += data
  138. else:
  139. self.channel.write(data)
  140. def connectionLost(self, reason):
  141. if self.channel:
  142. self.channel.loseConnection()
  143. self.channel = None
  144. def packOpen_direct_tcpip(destination, source):
  145. """
  146. Pack the data suitable for sending in a CHANNEL_OPEN packet.
  147. @type destination: L{tuple}
  148. @param destination: A tuple of the (host, port) of the destination host.
  149. @type source: L{tuple}
  150. @param source: A tuple of the (host, port) of the source host.
  151. """
  152. (connHost, connPort) = destination
  153. (origHost, origPort) = source
  154. if isinstance(connHost, unicode):
  155. connHost = connHost.encode("utf-8")
  156. if isinstance(origHost, unicode):
  157. origHost = origHost.encode("utf-8")
  158. conn = common.NS(connHost) + struct.pack('>L', connPort)
  159. orig = common.NS(origHost) + struct.pack('>L', origPort)
  160. return conn + orig
  161. packOpen_forwarded_tcpip = packOpen_direct_tcpip
  162. def unpackOpen_direct_tcpip(data):
  163. """Unpack the data to a usable format.
  164. """
  165. connHost, rest = common.getNS(data)
  166. if _PY3 and isinstance(connHost, bytes):
  167. connHost = connHost.decode("utf-8")
  168. connPort = int(struct.unpack('>L', rest[:4])[0])
  169. origHost, rest = common.getNS(rest[4:])
  170. if _PY3 and isinstance(origHost, bytes):
  171. origHost = origHost.decode("utf-8")
  172. origPort = int(struct.unpack('>L', rest[:4])[0])
  173. return (connHost, connPort), (origHost, origPort)
  174. unpackOpen_forwarded_tcpip = unpackOpen_direct_tcpip
  175. def packGlobal_tcpip_forward(peer):
  176. """
  177. Pack the data for tcpip forwarding.
  178. @param peer: A tuple of the (host, port) .
  179. @type peer: L{tuple}
  180. """
  181. (host, port) = peer
  182. return common.NS(host) + struct.pack('>L', port)
  183. def unpackGlobal_tcpip_forward(data):
  184. host, rest = common.getNS(data)
  185. if _PY3 and isinstance(host, bytes):
  186. host = host.decode("utf-8")
  187. port = int(struct.unpack('>L', rest[:4])[0])
  188. return host, port
  189. """This is how the data -> eof -> close stuff /should/ work.
  190. debug3: channel 1: waiting for connection
  191. debug1: channel 1: connected
  192. debug1: channel 1: read<=0 rfd 7 len 0
  193. debug1: channel 1: read failed
  194. debug1: channel 1: close_read
  195. debug1: channel 1: input open -> drain
  196. debug1: channel 1: ibuf empty
  197. debug1: channel 1: send eof
  198. debug1: channel 1: input drain -> closed
  199. debug1: channel 1: rcvd eof
  200. debug1: channel 1: output open -> drain
  201. debug1: channel 1: obuf empty
  202. debug1: channel 1: close_write
  203. debug1: channel 1: output drain -> closed
  204. debug1: channel 1: rcvd close
  205. debug3: channel 1: will not send data after close
  206. debug1: channel 1: send close
  207. debug1: channel 1: is dead
  208. """