#!/usr/bin/env python # -*- coding: utf-8 -*- import paramiko import socket import sys import time import argparse import getpass import logging import select import json import os from threading import Thread # 设置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class SSHClient: def __init__(self, server, port, username, password=None, key_file=None, timeout=30): """ Initialize SSH client Args: server (str): SSH server hostname or IP address port (int): SSH server port username (str): SSH username password (str, optional): SSH password key_file (str, optional): Path to private key file timeout (int, optional): Connection timeout in seconds """ self.server = server self.port = port self.username = username self.password = password self.key_file = key_file self.client = None self.timeout = timeout self.transport = None def connect(self): """ Establish connection to SSH server Returns: bool: True if connection successful, False otherwise """ try: logger.info(f"尝试连接到 {self.server}:{self.port}...") self.client = paramiko.SSHClient() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) connect_kwargs = { 'hostname': self.server, 'port': self.port, 'username': self.username, 'timeout': self.timeout, 'allow_agent': False, 'look_for_keys': False } if self.password: connect_kwargs['password'] = self.password elif self.key_file: connect_kwargs['key_filename'] = self.key_file # 尝试连接 self.client.connect(**connect_kwargs) self.transport = self.client.get_transport() # 设置保活 if self.transport: self.transport.set_keepalive(60) # 每60秒发送保活包 logger.info(f"成功连接到 {self.server}:{self.port} 用户名: {self.username}") return True except paramiko.AuthenticationException: logger.error("认证失败,请检查用户名和密码") return False except paramiko.SSHException as e: logger.error(f"SSH连接错误: {e}") return False except socket.timeout: logger.error(f"连接到 {self.server}:{self.port} 超时。请检查服务器地址和防火墙设置。") return False except socket.error as e: logger.error(f"socket错误: {e}") return False except Exception as e: logger.error(f"连接到SSH服务器时出错: {e}") import traceback logger.debug(traceback.format_exc()) return False def setup_port_forward(self, remote_host, remote_port, local_port): """ Set up port forwarding from server to client (remote to local) Args: remote_host (str): Remote host to connect to from the SSH server remote_port (int): Remote port to connect to local_port (int): Local port to forward to Returns: bool: True if port forwarding set up successfully, False otherwise """ try: # 确保传输层已经准备好 if not self.transport or not self.transport.is_active(): logger.error("SSH传输层未激活,无法设置端口转发") return False # 使用reverse_forward_tunnel方法来建立从服务器到客户端的转发 try: logger.info(f"尝试请求端口转发: {remote_host}:{remote_port}") self.transport.request_port_forward(remote_host, remote_port) except paramiko.SSHException as e: error_msg = str(e).lower() if "forwarding request denied" in error_msg or "addressnotpermitted" in error_msg: logger.error(f"端口转发请求被拒绝: {e}") logger.info("BvSshServer端口转发问题排查: ") logger.info("1. 检查用户权限: 确认SSH用户账户是否有端口转发权限") logger.info("2. 尝试使用不同的远程端口: 有些端口可能被禁止转发") logger.info("3. 查看服务器日志: 可能有更多关于拒绝原因的信息") logger.info("4. 检查是否有其他应用已经占用了该端口") logger.info("5. 尝试使用其他绑定地址,如 'localhost' 而不是 '127.0.0.1'") return False else: raise logger.info(f"设置端口转发: {remote_host}:{remote_port} -> localhost:{local_port}") # 创建一个监听线程来处理转发的连接 class ForwardServer(Thread): def __init__(self, transport, remote_host, remote_port, local_port): Thread.__init__(self) self.transport = transport self.remote_host = remote_host self.remote_port = remote_port self.local_port = local_port self.daemon = True def run(self): while True: try: chan = self.transport.accept(1000) if chan is None: continue # 建立从通道到本地端口的连接 thr = Thread(target=self.handler, args=(chan,)) thr.daemon = True thr.start() except Exception as e: if self.transport.is_active(): logger.error(f"转发通道接收错误: {e}") else: break def handler(self, chan): try: sock = socket.socket() try: sock.connect(('127.0.0.1', self.local_port)) except ConnectionRefusedError: logger.error(f"连接本地端口 {self.local_port} 被拒绝,请确保本地服务正在运行") chan.close() return logger.info(f"转发连接 {self.remote_host}:{self.remote_port} -> localhost:{self.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"转发处理错误: {e}") finally: try: sock.close() chan.close() except: pass # 启动转发服务器 forward_server = ForwardServer(self.transport, remote_host, remote_port, local_port) forward_server.start() return True except Exception as e: logger.error(f"设置端口转发时出错: {e}") # 检查是否包含 BvSshServer 特定的错误信息 error_str = str(e) if "AddressNotPermitted" in error_str or " localhost:{config['local_port']}") logger.info("按 Ctrl+C 退出...") # Keep the connection alive with transport keepalives while True: if not ssh_client.transport or not ssh_client.transport.is_active(): logger.error("SSH连接已断开,尝试重新连接...") if ssh_client.connect(): if not ssh_client.setup_port_forward( config.get('remote_host', 'localhost'), config['remote_port'], config['local_port'] ): logger.error("无法重新设置端口转发,退出程序") sys.exit(1) logger.info("连接和端口转发已恢复") else: logger.error("无法重新连接,退出程序") sys.exit(1) time.sleep(5) except KeyboardInterrupt: logger.info("\n正在退出...") except Exception as e: logger.error(f"发生错误: {e}") import traceback logger.debug(traceback.format_exc()) finally: ssh_client.close() if __name__ == "__main__": main()