ParallelSSH / parallel-ssh

Asynchronous parallel SSH client library.
https://parallel-ssh.org
GNU Lesser General Public License v2.1
1.21k stars 149 forks source link

Local SSH Port Forwarding (Tunneling) Example #383

Open nhansendev opened 1 year ago

nhansendev commented 1 year ago

Is your feature request related to a problem? Please describe. I'm trying to determine if/how parallel-ssh can be used for local port forwarding (tunneling) in a way that allows for arbitrary socket connections. The description for the Tunnel Server sounds exactly like what I need (I think), but I do not see any clear documentation/tutorials on its usage.

Local port forwarding server for tunneling connections from remote SSH server.
Accepts connections on an available bind_address port once started and tunnels data to/from remote SSH host for each connection.

I've tried the following, but while the SSH client connection is accepted, the socket is rejected:

from pssh.clients import SSHClient
from pssh.clients.native.tunnel import TunnelServer
import socket

client = SSHClient(REMOTE_IP, UNAME)

serv = TunnelServer(client, HOST, PORT, bind_address=HOST)
serv.start()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))

ConnectionRefusedError: [Errno 111] Connection refused

Describe the solution you'd like An example showing the correct usage of a TunnelServer for SSH local port forwarding.

Describe alternatives you've considered Using a different library, but I am not able to for my project.

nhansendev commented 1 year ago

I figured out what I was doing wrong.

  1. Use serv.serve_forever() instead of serv.start()
  2. Run it in a thread
  3. Connect the socket on the serv.listen_port instead

Working class example:

from pssh.clients import SSHClient
from pssh.clients.native.tunnel import TunnelServer
import threading, time

class PsshTunnel:
    def __init__(self, remote_ip, username, host="localhost", port=8080):
        self.remote_ip = remote_ip
        self.username = username
        self.host = host
        self.port = port

    def _run(self):
        client = SSHClient(self.remote_ip, self.username)

        self.serv = TunnelServer(client, self.host, self.port, bind_address=self.host)
        self.serv.serve_forever()

    def start(self):
        self.th = threading.Thread(target=self._run, daemon=True)
        self.th.start()
        # Give it time to connect
        time.sleep(3)

    def stop(self):
        self.serv.stop()

    @property
    def socket_port(self):
        return self.serv.listen_port