Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| import subprocess | |
| import threading | |
| import time | |
| import os | |
| import sys | |
| import logging | |
| import traceback | |
| import socket | |
| import select | |
| import fcntl | |
| # 设置日志 | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # 打印启动信息 | |
| logger.info("=" * 50) | |
| logger.info("VNC SSH反向隧道服务启动") | |
| logger.info(f"当前工作目录: {os.getcwd()}") | |
| logger.info(f"Python版本: {sys.version}") | |
| logger.info("=" * 50) | |
| # 隧道配置 | |
| SSH_SERVER = "www.ip188.net" | |
| SSH_PORT = 30001 | |
| SSH_USERNAME = "aauser" | |
| SSH_PASSWORD = "aa123456" | |
| LOCAL_PORT = 5901 # 本地VNC端口 | |
| REMOTE_PORT = 13698 # 远程暴露端口 | |
| def read_nonblocking(pipe): | |
| """非阻塞读取管道内容""" | |
| output = "" | |
| if pipe: | |
| # 设置非阻塞模式 | |
| flags = fcntl.fcntl(pipe, fcntl.F_GETFL) | |
| fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK) | |
| # 使用select检查是否有数据可读 | |
| reads, _, _ = select.select([pipe], [], [], 0) | |
| if pipe in reads: | |
| try: | |
| data = pipe.read() | |
| if data: | |
| output = data.decode('utf-8', errors='replace') | |
| except (OSError, IOError) as e: | |
| logger.error(f"读取管道错误: {e}") | |
| return output | |
| def create_ssh_tunnel_paramiko(): | |
| """使用paramiko库创建SSH反向隧道""" | |
| try: | |
| import paramiko | |
| logger.info("使用paramiko库创建SSH反向隧道") | |
| while True: | |
| try: | |
| # 创建SSH客户端 | |
| ssh_client = paramiko.SSHClient() | |
| ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |
| logger.info(f"连接到SSH服务器 {SSH_SERVER}:{SSH_PORT}...") | |
| ssh_client.connect( | |
| SSH_SERVER, | |
| port=SSH_PORT, | |
| username=SSH_USERNAME, | |
| password=SSH_PASSWORD, | |
| timeout=60, | |
| allow_agent=False, | |
| look_for_keys=False | |
| ) | |
| # 保活设置 | |
| transport = ssh_client.get_transport() | |
| transport.set_keepalive(60) | |
| # 设置远程转发 | |
| logger.info(f"建立反向隧道 localhost:{LOCAL_PORT} -> {REMOTE_PORT}") | |
| transport.request_port_forward("", REMOTE_PORT) | |
| # 处理转发连接的类 | |
| class ForwardServer(threading.Thread): | |
| def __init__(self, transport): | |
| threading.Thread.__init__(self) | |
| self.transport = transport | |
| self.daemon = True | |
| def run(self): | |
| logger.info("SSH VNC隧道已建立,等待连接...") | |
| try: | |
| while transport.is_active(): | |
| try: | |
| chan = transport.accept(60) | |
| if chan is None: | |
| continue | |
| # 为每个连接创建线程处理 | |
| thr = threading.Thread(target=self.handler, args=(chan,)) | |
| thr.daemon = True | |
| thr.start() | |
| except Exception as e: | |
| if transport.is_active(): | |
| logger.error(f"处理隧道连接时出错: {e}") | |
| else: | |
| break | |
| logger.info("隧道传输已关闭") | |
| except Exception as e: | |
| logger.error(f"转发服务器错误: {e}") | |
| def handler(self, chan): | |
| try: | |
| sock = socket.socket() | |
| try: | |
| sock.connect(('127.0.0.1', LOCAL_PORT)) | |
| except ConnectionRefusedError: | |
| logger.error(f"连接本地VNC端口 {LOCAL_PORT} 被拒绝") | |
| chan.close() | |
| return | |
| logger.info(f"处理转发连接: {REMOTE_PORT} -> localhost:{LOCAL_PORT}") | |
| # 在两个方向上转发数据 | |
| while True: | |
| r, w, x = select.select([sock, chan], [], []) | |
| if sock in r: | |
| data = sock.recv(1024) | |
| if len(data) == 0: | |
| break | |
| chan.send(data) | |
| if chan in r: | |
| data = chan.recv(1024) | |
| if len(data) == 0: | |
| break | |
| sock.send(data) | |
| except Exception as e: | |
| logger.error(f"处理VNC连接时出错: {e}") | |
| finally: | |
| try: | |
| sock.close() | |
| chan.close() | |
| except: | |
| pass | |
| # 启动转发服务器 | |
| forward_server = ForwardServer(transport) | |
| forward_server.start() | |
| # 保持连接 | |
| while transport.is_active(): | |
| time.sleep(60) | |
| logger.info("SSH VNC隧道保持活跃状态") | |
| logger.info("SSH连接已关闭,准备重新连接") | |
| except Exception as e: | |
| logger.error(f"SSH VNC隧道错误: {e}") | |
| logger.error(traceback.format_exc()) | |
| time.sleep(10) # 等待10秒后重试 | |
| except ImportError: | |
| logger.error("未找到paramiko库,尝试安装...") | |
| try: | |
| subprocess.run([sys.executable, "-m", "pip", "install", "paramiko"], check=True) | |
| logger.info("paramiko库安装成功,重新尝试创建隧道") | |
| create_ssh_tunnel_paramiko() # 递归调用自己 | |
| except Exception as install_error: | |
| logger.error(f"安装paramiko失败: {install_error}") | |
| create_ssh_tunnel_command() # 回退到命令行方式 | |
| def create_ssh_tunnel_command(): | |
| """使用ssh命令创建反向隧道""" | |
| logger.info("使用ssh命令创建VNC反向隧道") | |
| while True: | |
| try: | |
| # 检查ssh命令是否可用 | |
| ssh_check = subprocess.run(["which", "ssh"], capture_output=True, text=True) | |
| if ssh_check.returncode != 0: | |
| logger.error("未找到ssh命令,请确保已安装openssh-client") | |
| time.sleep(30) | |
| continue | |
| # 创建ssh命令 | |
| ssh_cmd = [ | |
| "ssh", | |
| "-o", "StrictHostKeyChecking=no", | |
| "-o", "UserKnownHostsFile=/dev/null", | |
| "-N", | |
| "-R", f"{REMOTE_PORT}:localhost:{LOCAL_PORT}", | |
| "-p", str(SSH_PORT), | |
| f"{SSH_USERNAME}@{SSH_SERVER}" | |
| ] | |
| # 使用sshpass或expect自动提供密码 | |
| use_sshpass = False | |
| sshpass_check = subprocess.run(["which", "sshpass"], capture_output=True, text=True) | |
| if sshpass_check.returncode == 0: | |
| use_sshpass = True | |
| ssh_cmd = ["sshpass", "-p", SSH_PASSWORD] + ssh_cmd | |
| logger.info("使用sshpass提供密码") | |
| logger.info(f"执行SSH命令: {' '.join(ssh_cmd if not use_sshpass else ['sshpass', '-p', '********'] + ssh_cmd[3:])}") | |
| # 执行SSH命令 | |
| process = subprocess.Popen( | |
| ssh_cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| stdin=subprocess.PIPE if not use_sshpass else None | |
| ) | |
| # 如果不使用sshpass,手动提供密码 | |
| if not use_sshpass: | |
| try: | |
| process.stdin.write(f"{SSH_PASSWORD}\n".encode()) | |
| process.stdin.flush() | |
| except: | |
| logger.error("无法提供密码") | |
| logger.info("SSH VNC隧道已启动") | |
| # 监控进程 | |
| while process.poll() is None: | |
| # 读取输出 | |
| stdout_data = read_nonblocking(process.stdout) | |
| stderr_data = read_nonblocking(process.stderr) | |
| if stdout_data: | |
| logger.info(f"SSH输出: {stdout_data.strip()}") | |
| if stderr_data: | |
| logger.error(f"SSH错误: {stderr_data.strip()}") | |
| time.sleep(5) | |
| # 进程已结束 | |
| exit_code = process.poll() | |
| logger.info(f"SSH VNC隧道进程退出,退出码: {exit_code}") | |
| # 重新启动 | |
| time.sleep(10) | |
| except Exception as e: | |
| logger.error(f"创建SSH VNC隧道时出错: {e}") | |
| logger.error(traceback.format_exc()) | |
| time.sleep(10) | |
| def main(): | |
| """主函数""" | |
| logger.info("启动SSH VNC反向隧道服务") | |
| # 首先尝试使用paramiko库 | |
| try: | |
| # 检查是否安装了paramiko | |
| import paramiko | |
| create_ssh_tunnel_paramiko() | |
| except ImportError: | |
| logger.warning("未找到paramiko库,尝试使用ssh命令") | |
| create_ssh_tunnel_command() | |
| if __name__ == "__main__": | |
| try: | |
| main() | |
| except Exception as e: | |
| logger.error(f"主程序发生错误: {e}") | |
| logger.error(traceback.format_exc()) | |
| sys.exit(1) |