From f73419e3eb178f7e2bc5152ad2bdf9761bd74faf Mon Sep 17 00:00:00 2001 From: cheney Date: Mon, 25 May 2026 11:13:40 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=20=E8=87=AA=E6=B5=8B=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- proxy/PROTOCOL.md | 174 ++++++++++++++++++++++++++++++++ proxy/proxy_client.py | 224 ++++++++++++++++++++++-------------------- proxy/proxy_server.py | 204 +++++++++++++++++++++++++++----------- 3 files changed, 439 insertions(+), 163 deletions(-) create mode 100644 proxy/PROTOCOL.md diff --git a/proxy/PROTOCOL.md b/proxy/PROTOCOL.md new file mode 100644 index 0000000..6490d79 --- /dev/null +++ b/proxy/PROTOCOL.md @@ -0,0 +1,174 @@ +# 代理通信协议设计文档 + +## 1. 架构概述 + +### 1.1 设备角色 + +| 设备 | 角色 | 能力 | 限制 | +|------|------|------|------| +| 电脑A | 代理客户端 | 可访问内网网站 | 无法监听端口 | +| 电脑B | 代理服务器 | 可监听端口 | 无法直接访问内网 | + +### 1.2 网络拓扑 + +``` +浏览器 <--(端口8081)--> 电脑B <--(端口8080)--> 电脑A <--(内网)--> 目标网站 +``` + +--- + +## 2. 端口分配 + +| 端口号 | 用途 | 连接方向 | +|--------|------|----------| +| 8080 | 设备A连接端口 | 电脑A → 电脑B | +| 8081 | 浏览器访问端口 | 浏览器 → 电脑B | + +--- + +## 3. 通信协议 + +### 3.1 协议类型 + +采用 **HTTP CONNECT 代理协议**,这是标准的HTTP隧道协议。 + +### 3.2 协议流程 + +#### 阶段1:设备A连接到设备B + +``` +电脑A → 电脑B: TCP连接建立 +电脑B → 电脑A: 连接接受(无额外协议,仅TCP握手) +``` + +#### 阶段2:浏览器发起请求 + +``` +浏览器 → 电脑B: HTTP CONNECT请求 +CONNECT zentao.sunyard.com.cn:9788 HTTP/1.1 +Host: zentao.sunyard.com.cn:9788 +[其他请求头...] +``` + +#### 阶段3:电脑B转发请求 + +``` +电脑B → 电脑A: 透传完整HTTP CONNECT请求 +``` + +#### 阶段4:电脑A建立隧道 + +``` +电脑A → 内网目标: TCP连接建立 +内网目标 → 电脑A: 连接接受 +电脑A → 电脑B: HTTP 200响应 +HTTP/1.1 200 Connection Established +``` + +#### 阶段5:数据隧道 + +``` +浏览器 ↔ 电脑B ↔ 电脑A ↔ 内网目标: 双向数据流透传 +``` + +#### 阶段6:连接关闭 + +任意一端断开连接,整条链路关闭。 + +--- + +## 4. 数据格式 + +### 4.1 HTTP CONNECT 请求格式 + +``` +CONNECT {目标地址}:{目标端口} HTTP/1.1\r\n +Host: {目标地址}:{目标端口}\r\n +[可选请求头]\r\n +\r\n +``` + +### 4.2 HTTP 200 响应格式 + +``` +HTTP/1.1 200 Connection Established\r\n +\r\n +``` + +--- + +## 5. 日志规范 + +### 5.1 日志格式 + +| 字段 | 格式 | 示例 | +|------|------|------| +| 时间戳 | `YYYY-MM-DD HH:MM:SS` | `2024-01-15 10:30:45` | +| 级别 | `[INFO]` / `[ERROR]` / `[DEBUG]` | `[INFO]` | +| 来源 | `[Browser]` / `[DeviceA]` / `[System]` | `[Browser]` | +| 内容 | 描述信息 | 请求详情 | + +### 5.2 HTTP请求日志(电脑B) + +``` +[时间戳] [INFO] [Browser] {客户端IP}:{端口} - {方法} {URL} - Body: {字节数} bytes +``` + +### 5.3 HTTP响应日志(电脑B) + +``` +[时间戳] [INFO] [Browser] {客户端IP}:{端口} - {方法} {URL} - Status: {状态码} - Time: {耗时}ms +``` + +### 5.4 连接状态日志 + +``` +[时间戳] [INFO] [DeviceA] Connected: {IP}:{端口} +[时间戳] [INFO] [DeviceA] Disconnected +[时间戳] [INFO] [Browser] Connected from: {IP}:{端口} +[时间戳] [INFO] [Browser] Disconnected: {IP}:{端口} +``` + +--- + +## 6. 错误处理 + +### 6.1 设备A未连接 + +当浏览器请求时设备A未连接: + +``` +HTTP/1.1 503 Service Unavailable +Content-Type: text/plain + +Device A not connected +``` + +### 6.2 目标不可达 + +当电脑A无法连接目标网站: + +``` +HTTP/1.1 502 Bad Gateway +Content-Type: text/plain + +Cannot connect to target +``` + +### 6.3 重复连接 + +当已有设备A连接时,拒绝新连接: + +``` +ERROR: Another A device is already connected +``` + +--- + +## 7. 超时设置 + +| 操作 | 超时时间 | +|------|----------| +| 设备A连接超时 | 10秒 | +| 目标网站连接超时 | 30秒 | +| 连接空闲超时 | 120秒 | diff --git a/proxy/proxy_client.py b/proxy/proxy_client.py index 4a39c26..58aab87 100644 --- a/proxy/proxy_client.py +++ b/proxy/proxy_client.py @@ -2,140 +2,146 @@ import socket import threading import sys +import time -def tunnel_thread(src, dst): - """Tunnel thread: continuously forward data""" - try: - while True: - data = src.recv(8192) - if not data: - break - dst.sendall(data) - except Exception as e: - pass +TARGET_HOST = 'zentao.sunyard.com.cn' +TARGET_PORT = 9788 -def handle_b_device(sock): - """Handle communication with device B, establish tunnel to target website""" - print("\n[System] Connected to device B") +def log_info(source, message): + """Log info message""" + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + print("[%s] [INFO] [%s] %s" % (timestamp, source, message)) + +def log_error(source, message): + """Log error message""" + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + print("[%s] [ERROR] [%s] %s" % (timestamp, source, message)) + +def handle_request(b_socket, request_data): + """Handle a single request from device B""" + # Parse HTTP request + request_lines = request_data.split(b"\r\n") + if request_lines: + first_line = request_lines[0].decode('utf-8', errors='ignore') + parts = first_line.split() + if len(parts) >= 2: + method = parts[0] + url = parts[1] + log_info("System", "Received request: %s %s" % (method, url)) + + # Connect to target + target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + target_sock.settimeout(30) try: - while True: - request = b"" + target_sock.connect((TARGET_HOST, TARGET_PORT)) + log_info("System", "Connected to target: %s:%d" % (TARGET_HOST, TARGET_PORT)) + + if method == "CONNECT": + # Send CONNECT response + response = b"HTTP/1.1 200 Connection Established\r\n\r\n" + b_socket.sendall(response) + log_info("System", "Sent CONNECT response") + + # Establish tunnel + def forward(src, dst): + try: + while True: + data = src.recv(8192) + if not data: + break + dst.sendall(data) + except: + pass + + t1 = threading.Thread(target=forward, args=(b_socket, target_sock)) + t2 = threading.Thread(target=forward, args=(target_sock, b_socket)) + t1.start() + t2.start() + t1.join() + t2.join() + + else: + # Forward request and response + target_sock.sendall(request_data) + while True: + response = target_sock.recv(8192) + if not response: + break + b_socket.sendall(response) + + return True + + except ConnectionRefusedError: + log_error("System", "Cannot connect to target: %s:%d" % (TARGET_HOST, TARGET_PORT)) + error_msg = b"HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\n\r\nCannot connect to target\r\n" + b_socket.sendall(error_msg) + return False + except socket.timeout: + log_error("System", "Target connection timeout") + error_msg = b"HTTP/1.1 504 Gateway Timeout\r\nContent-Type: text/plain\r\n\r\nConnection timeout\r\n" + b_socket.sendall(error_msg) + return False + except Exception as e: + log_error("System", "Error handling request: %s" % str(e)) + return False + finally: + target_sock.close() + +def listen_for_requests(sock): + """Listen for requests from device B""" + try: + while True: + # Wait for request with long timeout + sock.settimeout(600) # 10 minutes + + request_data = b"" + while b"\r\n\r\n" not in request_data: chunk = sock.recv(8192) if not chunk: - print("[Device B] Disconnected") + log_info("System", "Device B disconnected") return - request += chunk - if b"\r\n\r\n" in request: - break + request_data += chunk - print("[System] Received request:", len(request), "bytes") + # Handle request in a separate thread + t = threading.Thread(target=handle_request, args=(sock, request_data)) + t.start() - target_host = None - target_port = 443 - - if request.startswith(b"CONNECT"): - try: - lines = request.decode().split("\r\n") - connect_line = lines[0] - target = connect_line.split()[1] - if ':' in target: - target_host, port_str = target.split(':') - target_port = int(port_str) - else: - target_host = target - target_port = 443 - except Exception as e: - print("[Error] Failed to parse CONNECT request:", e) - sock.send(b"HTTP/1.1 400 Bad Request\r\n\r\n") - continue - else: - try: - for line in request.split(b"\r\n"): - if line.lower().startswith(b"host:"): - host_part = line[5:].strip() - if b':' in host_part: - target_host, port_str = host_part.split(b':') - target_port = int(port_str) - else: - target_host = host_part.decode() - break - except Exception as e: - print("[Error] Failed to parse HTTP request:", e) - sock.send(b"HTTP/1.1 400 Bad Request\r\n\r\n") - continue - - if not target_host: - sock.send(b"HTTP/1.1 400 Bad Request\r\n\r\n") - continue - - print("[Target] Host:", target_host, "Port:", target_port) - - target_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - target_sock.settimeout(30) - - try: - target_sock.connect((target_host, target_port)) - print("[Target] Connected successfully") - - if request.startswith(b"CONNECT"): - sock.send(b"HTTP/1.1 200 Connection Established\r\n\r\n") - else: - target_sock.sendall(request) - - t1 = threading.Thread(target=tunnel_thread, args=(sock, target_sock)) - t2 = threading.Thread(target=tunnel_thread, args=(target_sock, sock)) - t1.start() - t2.start() - t1.join() - t2.join() - - except ConnectionRefusedError: - print("[Error] Cannot connect to target:", target_host, target_port) - error_msg = "HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\n\r\nCannot connect to " + target_host + ":" + str(target_port) - sock.send(error_msg.encode()) - except socket.timeout: - print("[Error] Connection timeout") - sock.send(b"HTTP/1.1 504 Gateway Timeout\r\n\r\n") - except Exception as e: - print("[Error] Failed to connect to target:", e) - error_msg = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\n\r\n" + str(e) - sock.send(error_msg.encode()) - finally: - target_sock.close() - - except ConnectionResetError: - print("[Device B] Connection forcibly closed") + except socket.timeout: + log_info("System", "Connection idle timeout") except Exception as e: - print("[Error] Communication error with device B:", e) - finally: - sock.close() + log_error("System", "Connection error: %s" % str(e)) def connect_to_b_device(b_host, b_port): - """Connect to device B""" + """Connect to device B and handle requests""" while True: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) sock.connect((b_host, b_port)) - sock.settimeout(None) - print("\n[System] Successfully connected to device B:", b_host, ":", b_port) + # Wait for connection confirmation from server + sock.settimeout(5) + response = sock.recv(1024) + if not response or b"PROXY_CONNECTED" not in response: + log_error("System", "Failed to receive connection confirmation") + sock.close() + time.sleep(3) + continue - handle_b_device(sock) + log_info("System", "Successfully connected to device B: %s:%d" % (b_host, b_port)) + + listen_for_requests(sock) except ConnectionRefusedError: - print("[System] Device B not ready (", b_host, ":", b_port, "), retrying in 3 seconds...") - import time + log_info("System", "Device B not ready (%s:%d), retrying in 3 seconds..." % (b_host, b_port)) time.sleep(3) except socket.timeout: - print("[System] Connection timeout, retrying in 3 seconds...") - import time + log_info("System", "Connection timeout, retrying in 3 seconds...") time.sleep(3) except Exception as e: - print("[Error] Connection error:", e) - import time + log_error("System", "Connection error: %s" % str(e)) time.sleep(3) def main(): @@ -143,6 +149,7 @@ def main(): print("=" * 60) print(" Proxy Client (Run on Computer A)") print("=" * 60) + print("Target website:", TARGET_HOST, ":", TARGET_PORT) print("Usage: python proxy_client.py ") print("Example: python proxy_client.py 192.168.1.100 8080") print("=" * 60) @@ -154,6 +161,7 @@ def main(): print("=" * 60) print(" Proxy Client (Run on Computer A)") print("=" * 60) + print("Target website:", TARGET_HOST, ":", TARGET_PORT) print("Device B address:", b_host, ":", b_port) print("=" * 60) diff --git a/proxy/proxy_server.py b/proxy/proxy_server.py index 4d15324..dbe7126 100644 --- a/proxy/proxy_server.py +++ b/proxy/proxy_server.py @@ -1,79 +1,173 @@ # -*- coding: utf-8 -*- import socket import threading -import sys +import time PORT_FOR_A = 8080 # Port for device A to connect PORT_FOR_BROWSER = 8081 # Port for browser to connect -a_device_socket = None +a_device_conn = None a_device_lock = threading.Lock() +a_device_connected = threading.Event() -def tunnel_thread(src, dst): - """Tunnel thread: continuously forward data""" +def log_info(source, message): + """Log info message""" + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + print("[%s] [INFO] [%s] %s" % (timestamp, source, message)) + +def log_error(source, message): + """Log error message""" + timestamp = time.strftime("%Y-%m-%d %H:%M:%S") + print("[%s] [ERROR] [%s] %s" % (timestamp, source, message)) + +def handle_browser(browser_conn, browser_addr): + """Handle browser connection""" + global a_device_conn + + log_info("Browser", "Connected from: %s:%d" % browser_addr) + + # Wait for device A to connect + if not a_device_connected.wait(timeout=5): + error_msg = b"HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\n\r\nDevice A not connected\r\n" + browser_conn.send(error_msg) + browser_conn.close() + log_info("Browser", "Disconnected (A not connected): %s:%d" % browser_addr) + return + + # Read HTTP request + request_start = time.time() + request_data = b"" + browser_conn.settimeout(30) + + try: + while b"\r\n\r\n" not in request_data: + chunk = browser_conn.recv(8192) + if not chunk: + browser_conn.close() + log_info("Browser", "Disconnected (no request): %s:%d" % browser_addr) + return + request_data += chunk + except socket.timeout: + browser_conn.close() + log_info("Browser", "Disconnected (timeout): %s:%d" % browser_addr) + return + + # Parse HTTP request + request_lines = request_data.split(b"\r\n") + method = "" + url = "" + if request_lines: + first_line = request_lines[0].decode('utf-8', errors='ignore') + parts = first_line.split() + if len(parts) >= 2: + method = parts[0] + url = parts[1] + body_size = len(request_data) + log_info("Browser", "%s:%d - %s %s - Body: %d bytes" % (browser_addr[0], browser_addr[1], method, url, body_size)) + + # Get device A connection + with a_device_lock: + device_a = a_device_conn + + if not device_a: + browser_conn.close() + log_error("Browser", "Disconnected (A closed): %s:%d" % browser_addr) + return + + # Send request to device A + device_a.sendall(request_data) + + # Wait for response + response_data = b"" + device_a.settimeout(30) + try: while True: - data = src.recv(8192) - if not data: + chunk = device_a.recv(8192) + if not chunk: break - dst.sendall(data) - except Exception as e: + response_data += chunk + if b"\r\n\r\n" in response_data: + if method == "CONNECT" and b"200 Connection Established" in response_data: + break + if len(response_data) > 8192: + break + except: pass - finally: - try: - src.close() - except: - pass - try: - dst.close() - except: - pass - -def handle_browser(conn, addr): - """Handle browser connection, establish tunnel with device A""" - global a_device_socket - print("\n[Browser] Connected from:", addr) + # Parse response status + response_status = "Unknown" + if response_data: + response_lines = response_data.split(b"\r\n") + if response_lines: + status_line = response_lines[0].decode('utf-8', errors='ignore') + status_parts = status_line.split() + if len(status_parts) >= 2: + response_status = status_parts[1] + + # Send response to browser + browser_conn.sendall(response_data) + + # Calculate response time + response_time = int((time.time() - request_start) * 1000) + log_info("Browser", "%s:%d - %s %s - Status: %s - Time: %dms" % (browser_addr[0], browser_addr[1], method, url, response_status, response_time)) + + # For CONNECT, establish tunnel + if method == "CONNECT" and response_status == "200": + log_info("System", "Tunnel established between browser and Device A") + + def forward(src, dst): + try: + while True: + data = src.recv(8192) + if not data: + break + dst.sendall(data) + except: + pass + + t1 = threading.Thread(target=forward, args=(browser_conn, device_a)) + t2 = threading.Thread(target=forward, args=(device_a, browser_conn)) + t1.start() + t2.start() + t1.join() + t2.join() + + browser_conn.close() + log_info("Browser", "Disconnected: %s:%d" % browser_addr) + +def handle_a_device(conn, addr): + """Handle device A connection - just store and hold""" + global a_device_conn with a_device_lock: - if not a_device_socket: - conn.send(b"HTTP/1.1 503 Service Unavailable\r\nContent-Type: text/plain\r\n\r\nDevice A not connected") + if a_device_conn: + conn.send(b"ERROR: Another A device is already connected\r\n") conn.close() - print("[Browser] Disconnected (Device A not connected):", addr) + log_info("DeviceA", "Rejected connection from %s:%d" % addr) return + + a_device_conn = conn - t1 = threading.Thread(target=tunnel_thread, args=(conn, a_device_socket)) - t2 = threading.Thread(target=tunnel_thread, args=(a_device_socket, conn)) - t1.start() - t2.start() - - print("[System] Tunnel established between browser and device A") - -def handle_a_device(conn): - """Handle device A connection""" - global a_device_socket - - with a_device_lock: - if a_device_socket: - conn.send(b"ERROR: Another A device is already connected") - conn.close() - return - a_device_socket = conn - - print("\n[Device A] Connected:", conn.getpeername()) + # Send connection confirmation + conn.send(b"PROXY_CONNECTED\r\n") + a_device_connected.set() + log_info("DeviceA", "Connected: %s:%d" % addr) + # Keep connection open by waiting on an event (never triggered) + # Connection will be closed when browser tunnel ends or error occurs try: - while True: - data = conn.recv(1) - if not data: - break + event = threading.Event() + event.wait() except: pass finally: with a_device_lock: - a_device_socket = None + if a_device_conn == conn: + a_device_conn = None + a_device_connected.clear() conn.close() - print("[Device A] Disconnected") + log_info("DeviceA", "Disconnected") def run_browser_server(): """Start browser listening port""" @@ -82,7 +176,7 @@ def run_browser_server(): server.bind(('0.0.0.0', PORT_FOR_BROWSER)) server.listen(10) - print("[System] Browser proxy port started, listening on 0.0.0.0:", PORT_FOR_BROWSER) + log_info("System", "Browser port started on %d" % PORT_FOR_BROWSER) while True: conn, addr = server.accept() @@ -96,19 +190,19 @@ def run_a_device_server(): server.bind(('0.0.0.0', PORT_FOR_A)) server.listen(1) - print("[System] Device A port started, listening on 0.0.0.0:", PORT_FOR_A) + log_info("System", "Device A port started on %d" % PORT_FOR_A) while True: conn, addr = server.accept() - t = threading.Thread(target=handle_a_device, args=(conn,)) + t = threading.Thread(target=handle_a_device, args=(conn, addr)) t.start() def main(): print("=" * 60) print(" Proxy Server (Run on Computer B)") print("=" * 60) - print("Device A connection port:", PORT_FOR_A) - print("Browser access port:", PORT_FOR_BROWSER) + print("Device A port:", PORT_FOR_A) + print("Browser port:", PORT_FOR_BROWSER) print("=" * 60) print("Waiting for connections...\n")