Files
call-center/scripts/asterisk_ivr_reverse_tunnel.py

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())