100 lines
3.2 KiB
Python
100 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import select
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
import paramiko
|
|
|
|
|
|
def _bridge_channels(chan: paramiko.Channel, upstream: socket.socket) -> None:
|
|
try:
|
|
while True:
|
|
readers, _, _ = select.select([chan, upstream], [], [], 1.0)
|
|
if chan in readers:
|
|
data = chan.recv(4096)
|
|
if not data:
|
|
break
|
|
upstream.sendall(data)
|
|
if upstream in readers:
|
|
data = upstream.recv(4096)
|
|
if not data:
|
|
break
|
|
chan.sendall(data)
|
|
finally:
|
|
try:
|
|
chan.close()
|
|
except Exception:
|
|
pass
|
|
try:
|
|
upstream.close()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _accept_channels(transport: paramiko.Transport, local_host: str, local_port: int) -> None:
|
|
while transport.is_active():
|
|
chan = transport.accept(1000)
|
|
if chan is None:
|
|
continue
|
|
try:
|
|
upstream = socket.create_connection((local_host, local_port), timeout=10)
|
|
except OSError:
|
|
chan.close()
|
|
continue
|
|
threading.Thread(target=_bridge_channels, args=(chan, upstream), daemon=True).start()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Maintain reverse SSH tunnel for Asterisk IVR FastAGI.")
|
|
parser.add_argument("--ssh-host", required=True)
|
|
parser.add_argument("--ssh-port", type=int, default=22)
|
|
parser.add_argument("--ssh-user", required=True)
|
|
parser.add_argument("--ssh-password", required=True)
|
|
parser.add_argument("--remote-bind-host", default="127.0.0.1")
|
|
parser.add_argument("--remote-port", type=int, default=4573)
|
|
parser.add_argument("--local-host", default="127.0.0.1")
|
|
parser.add_argument("--local-port", type=int, default=4573)
|
|
args = parser.parse_args()
|
|
|
|
while True:
|
|
client = paramiko.SSHClient()
|
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
try:
|
|
client.connect(
|
|
args.ssh_host,
|
|
port=args.ssh_port,
|
|
username=args.ssh_user,
|
|
password=args.ssh_password,
|
|
timeout=15,
|
|
banner_timeout=15,
|
|
auth_timeout=15,
|
|
)
|
|
transport = client.get_transport()
|
|
if transport is None:
|
|
raise RuntimeError("SSH transport is unavailable")
|
|
transport.request_port_forward(args.remote_bind_host, args.remote_port)
|
|
print(
|
|
f"Reverse tunnel active: {args.remote_bind_host}:{args.remote_port} -> "
|
|
f"{args.local_host}:{args.local_port}",
|
|
flush=True,
|
|
)
|
|
_accept_channels(transport, args.local_host, args.local_port)
|
|
except KeyboardInterrupt:
|
|
return 0
|
|
except Exception as exc:
|
|
print(f"Reverse tunnel error: {exc}", file=sys.stderr, flush=True)
|
|
time.sleep(3)
|
|
finally:
|
|
try:
|
|
client.close()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|