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>
88 lines
2.5 KiB
Python
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")
|