Files
pyssh-client/tests/test_tunnels.py
snoopy 9d346d76ed Tunnels SSH, envoi multi-onglets, et reparation de main_window.py
Tunnels (redirection de ports), la specialite de Bitvise : un bouton dans la
barre d'un onglet SSH ouvre un gestionnaire de redirections.
- locale (-L) : un port de votre poste est relaye, via la connexion SSH, vers
  une machine joignable depuis le serveur (typiquement un service qui n'ecoute
  que sur la boucle locale du serveur)
- distante (-R) : l'inverse
Chaque tunnel tourne dans son propre thread, un relais bloque ne genant ni
l'interface ni les autres tunnels. Ils sont fermes avec la session.

Envoi simultane, façon MobaXterm : le bouton "Multi" diffuse la frappe vers
tous les onglets terminal. La diffusion ecrit directement dans les connexions
et non via les widgets terminal, sinon chaque onglet rediffuserait a son tour.

Reparation : un de mes scripts de correction avait reecrit main_window.py en
retraduisant deux fois les fins de ligne, produisant des sequences \r\r\n ;
le fichier avait ete committe ainsi (2538 lignes au lieu de 856, une ligne vide
inseree partout). Le contenu etait intact, seule la mise en forme etait touchee.
Le fichier est reconstitue par normalisation des fins de ligne, et un controle
d'integrite a ete ajoute a la verification de non-regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 11:52:25 +02:00

88 lines
2.5 KiB
Python

"""Tunnels SSH : la redirection locale relaie-t-elle reellement le trafic ?
Le test monte un vrai serveur TCP local et un faux transport SSH dont le canal
'direct-tcpip' est un simple socket vers ce serveur : cela valide toute la
mecanique de relais sans avoir besoin d'un serveur SSH.
"""
import sys, pathlib
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import socket
import threading
import time
from app.tunnels import TunnelManager, DIRECTION_LOCAL, describe
class EchoServer(threading.Thread):
"""Serveur de destination : renvoie ce qu'il recoit, prefixe."""
def __init__(self):
super().__init__(daemon=True)
self.sock = socket.socket()
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind(("127.0.0.1", 0))
self.sock.listen(5)
self.port = self.sock.getsockname()[1]
def run(self):
while True:
try:
conn, _ = self.sock.accept()
except OSError:
return
data = conn.recv(1024)
conn.sendall(b"echo:" + data)
conn.close()
class FakeTransport:
"""Imite paramiko.Transport : ouvre un vrai socket vers la destination."""
def open_channel(self, kind, dest, src):
assert kind == "direct-tcpip"
return socket.create_connection(dest, timeout=5)
echo = EchoServer()
echo.start()
print(f"serveur de destination sur le port {echo.port}")
manager = TunnelManager(FakeTransport())
config = {
"direction": DIRECTION_LOCAL,
"listen_port": 0, # port libre choisi par le systeme
"dest_host": "127.0.0.1",
"dest_port": echo.port,
}
tunnel = manager.start(config)
assert tunnel.error is None, tunnel.error
port_local = tunnel._server.server_address[1]
print("tunnel local ouvert sur le port", port_local)
print("description :", describe({**config, "listen_port": port_local}))
time.sleep(0.3)
client = socket.create_connection(("127.0.0.1", port_local), timeout=5)
client.sendall(b"bonjour")
reponse = client.recv(1024)
client.close()
print("envoye : b'bonjour'")
print("recu :", reponse)
assert reponse == b"echo:bonjour", reponse
print()
print("=== fermeture du tunnel ===")
manager.stop_all()
time.sleep(0.3)
assert not manager.tunnels
try:
socket.create_connection(("127.0.0.1", port_local), timeout=1).close()
ferme = False
except OSError:
ferme = True
print("le port n'accepte plus de connexion :", ferme)
assert ferme, "le port doit etre libere apres fermeture"
print()
print("TUNNELS SSH : OK")