forward.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. """Sample script showing how to do local port forwarding over paramiko.
  2. This script connects to the requested SSH server and sets up local port
  3. forwarding (the openssh -L option) from a local port through a tunneled
  4. connection to a destination reachable from the SSH server machine.
  5. """
  6. #
  7. # This file is adapted from a paramiko demo, and thus licensed under LGPL 2.1.
  8. # Original Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
  9. # Edits Copyright (C) 2010 The IPython Team
  10. #
  11. # Paramiko is free software; you can redistribute it and/or modify it under the
  12. # terms of the GNU Lesser General Public License as published by the Free
  13. # Software Foundation; either version 2.1 of the License, or (at your option)
  14. # any later version.
  15. #
  16. # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
  17. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  18. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  19. # details.
  20. #
  21. # You should have received a copy of the GNU Lesser General Public License
  22. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  23. # 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA.
  24. import logging
  25. import select
  26. import socketserver
  27. import typing as t
  28. logger = logging.getLogger("ssh")
  29. class ForwardServer(socketserver.ThreadingTCPServer):
  30. """A server to use for ssh forwarding."""
  31. daemon_threads = True
  32. allow_reuse_address = True
  33. class Handler(socketserver.BaseRequestHandler):
  34. """A handle for server requests."""
  35. @t.no_type_check
  36. def handle(self):
  37. """Handle a request."""
  38. try:
  39. chan = self.ssh_transport.open_channel(
  40. "direct-tcpip",
  41. (self.chain_host, self.chain_port),
  42. self.request.getpeername(),
  43. )
  44. except Exception as e:
  45. logger.debug(
  46. "Incoming request to %s:%d failed: %s" % (self.chain_host, self.chain_port, repr(e))
  47. )
  48. return
  49. if chan is None:
  50. logger.debug(
  51. "Incoming request to %s:%d was rejected by the SSH server."
  52. % (self.chain_host, self.chain_port)
  53. )
  54. return
  55. logger.debug(
  56. f"Connected! Tunnel open {self.request.getpeername()!r} -> {chan.getpeername()!r} -> {(self.chain_host, self.chain_port)!r}"
  57. )
  58. while True:
  59. r, _w, _x = select.select([self.request, chan], [], [])
  60. if self.request in r:
  61. data = self.request.recv(1024)
  62. if len(data) == 0:
  63. break
  64. chan.send(data)
  65. if chan in r:
  66. data = chan.recv(1024)
  67. if len(data) == 0:
  68. break
  69. self.request.send(data)
  70. chan.close()
  71. self.request.close()
  72. logger.debug("Tunnel closed ")
  73. def forward_tunnel(local_port: int, remote_host: str, remote_port: int, transport: t.Any) -> None:
  74. """Forward an ssh tunnel."""
  75. # this is a little convoluted, but lets me configure things for the Handler
  76. # object. (SocketServer doesn't give Handlers any way to access the outer
  77. # server normally.)
  78. class SubHander(Handler):
  79. chain_host = remote_host
  80. chain_port = remote_port
  81. ssh_transport = transport
  82. ForwardServer(("127.0.0.1", local_port), SubHander).serve_forever()
  83. __all__ = ["forward_tunnel"]