Last active
December 15, 2023 10:45
-
-
Save tatome/0d3e6479f35b25bbb31c9f94610eab6b to your computer and use it in GitHub Desktop.
MWE for https://stackoverflow.com/questions/77661426/paramiko-request-port-forward-with-and-without-handler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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... |
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
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.