Skip to content

Instantly share code, notes, and snippets.

@tatome
Last active December 15, 2023 10:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tatome/0d3e6479f35b25bbb31c9f94610eab6b to your computer and use it in GitHub Desktop.
Save tatome/0d3e6479f35b25bbb31c9f94610eab6b to your computer and use it in GitHub Desktop.
# This script does what it's supposed to: it opens multiple tunnels with different ports on the SSH host and handles them
# concurrently and it can react to the port that is being accessed by the client.
import threading
import time
import paramiko
import select
import queue
user = input('username')
host = input('hostname')
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect(
host,
22,
username=user,
look_for_keys=True
)
request_queue = queue.Queue()
def handler():
while True:
request = request_queue.get()
if request:
channel, _, target_address = request
print("processing request from target address" + str(target_address))
r, *others = select.select([channel], [], [])
while r == [channel]:
print('receiving.')
r[0].recv(1024)
r, *others = select.select([channel], [], [], 1)
channel.send(f'Hello world {target_address[1]}.'.encode())
channel.close()
def dispatcher(channel, source_address, target_address):
global tunnel_channel
print("Queueing request.")
request_queue.put((channel, source_address, target_address))
def obsolete_dispatcher():
raise NotImplementedError("This dispatcher will never be called!")
transport = client.get_transport()
# It's a little unexpected that the second call to request_port_forward overrides the handler.
transport.request_port_forward('', 30000, obsolete_dispatcher)
transport.request_port_forward('', 30001, dispatcher)
for _ in range(3):
threading.Thread(target=handler).start()
while True:
time.sleep(1)
# Stripped down to an MWE from https://github.com/paramiko/paramiko/blob/main/demos/rforward.py
#
# This script transfers the first few bytes from the first request to the ssh client and exits; not useful,
# but works as expected.
#
# Problem with this: the port number is lost, so this can't be extended to multiple tunnels on different ports that
# are handled differently depending on the port that is being accessed
import paramiko
import select
user = input('username')
host = input('hostname')
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect(
host,
22,
username=user,
look_for_keys=True
)
def handler(channel, source_address, target_address):
print("Selecting...")
selects = select.select([channel],[],[])
print("Selected: " + str(selects))
transport = client.get_transport()
transport.request_port_forward('', 30000)
channel = transport.accept(1000)
handler(channel, None, None)
# Output:
# Selecting...
# Selected: ([<paramiko.Channel 0 (open) window=2097152 in-buffer=142 -> <paramiko.Transport at 0x262df700 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>], [], [])
# This script waits for the first request and then just hangs, trying to read the first few bytes from the request.
# Expected: it should transfer the first few bytes from every request to the ssh client (and then close the connection).
#
# The problem seems to be a threading issue in paramiko; confusing because the example script shows that connections can
# indeed be processed in threads.
import time
import paramiko
import select
user = input('username')
host = input('hostname')
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect(
host,
22,
username=user,
look_for_keys=True
)
def handler(channel, source_address, target_address):
print("Selecting...")
# The following line never returns.
selects = select.select([channel],[],[])
print("Selected: " + str(selects))
transport = client.get_transport()
transport.request_port_forward('', 30000, handler)
while True:
time.sleep(1)
# Output:
# Selecting...
@tatome
Copy link
Author

tatome commented Dec 15, 2023

Run either of these scripts, enter an SSH host name and your user name. Then, on a different shell, try wget <hostname>:30000.
The result should be the same but isn't.

@tatome
Copy link
Author

tatome commented Dec 15, 2023

The SO question was resolved and
paramiko_multiple_port_forwards_on_one_transport.py demonstrates how we can handle multiple port forwards with just one SSH client.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment