File size: 10,782 Bytes
e4859c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/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)