mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-05 15:46:45 +08:00
feat: implement comprehensive error diagnostics and structured error codes for VPN connectivity, API fetching, and proxy initialization.
This commit is contained in:
+2
-1
@@ -436,7 +436,8 @@ def print_status():
|
||||
print_line(format_line("出口 IP (出站)", proxy_ip))
|
||||
print_line(format_line("本地代理延迟", f"{proxy_latency} ms" if proxy_latency else "检测中..."))
|
||||
else:
|
||||
print_line(format_line("出口 IP (出站)", f"{red}[检测中/未就绪]{reset}"))
|
||||
proxy_err = state.get("proxy_error") or "检测中/未就绪"
|
||||
print_line(format_line("出口 IP (出站)", f"{red}[不可用 - {proxy_err}]{reset}"))
|
||||
else:
|
||||
print_line(format_line("节点状态", "无活动连接"))
|
||||
print_line()
|
||||
|
||||
+13
-2
@@ -51,7 +51,11 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
||||
sock.settimeout(timeout)
|
||||
try:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"tun0")
|
||||
except OSError:
|
||||
except OSError as e:
|
||||
if "operation not permitted" in str(e).lower() or e.errno == 1:
|
||||
print("[DNS 绑定失败] [错误代码 3006] DNS 解析绑定 tun0 权限不足,请确保程序以 root 权限运行!", flush=True)
|
||||
elif "no such device" in str(e).lower() or e.errno == 19:
|
||||
print("[DNS 绑定失败] [错误代码 3004] DNS 解析绑定 tun0 失败,网卡设备不存在,请检查 VPN 连接!", flush=True)
|
||||
return None
|
||||
sock.sendto(packet, (dns_server, 53))
|
||||
resp, _ = sock.recvfrom(2048)
|
||||
@@ -131,6 +135,10 @@ def create_connection(address: tuple[str, int], timeout: float = 20) -> socket.s
|
||||
return sock
|
||||
except OSError as e:
|
||||
err = e
|
||||
if "operation not permitted" in str(e).lower() or e.errno == 1:
|
||||
err = OSError(f"[错误代码 3006] [ERR_PROXY_BIND_TUN_PERM_DENIED] 绑定虚拟网卡 tun0 失败,权限不足!必须以 root 权限运行,或者进程缺少 CAP_NET_RAW 权限。")
|
||||
elif "no such device" in str(e).lower() or e.errno == 19:
|
||||
err = OSError(f"[错误代码 3004] [ERR_ROUTE_DEV_NOT_FOUND] 绑定虚拟网卡 tun0 失败,找不到设备!这通常是因为 OpenVPN 核心未能成功连接或已被异常终止。")
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
if err is not None:
|
||||
@@ -255,7 +263,10 @@ def start_proxy_server(host: str, port: int) -> None:
|
||||
server.listen(256)
|
||||
print(f"HTTP/SOCKS5 proxy listening on {host}:{port}", flush=True)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to start HTTP/SOCKS5 proxy on {host}:{port}: {e}", flush=True)
|
||||
import vpn_utils
|
||||
diag = vpn_utils.diagnose_local_obstructions(port)
|
||||
diag_msg = diag[1] if diag else str(e)
|
||||
print(f"[ERROR] Failed to start HTTP/SOCKS5 proxy on {host}:{port}: {diag_msg}", flush=True)
|
||||
return
|
||||
|
||||
while True:
|
||||
|
||||
+592
-422
File diff suppressed because it is too large
Load Diff
+31
-15
@@ -333,8 +333,16 @@ def fetch_candidates() -> list[dict[str, Any]]:
|
||||
print(f"[fetch_candidates] Fetch {i+1} failed: {e}", flush=True)
|
||||
log_to_json("WARNING", "Main", f"第 {i+1} 次拉取 API 节点失败: {e}")
|
||||
if i == max_attempts - 1 and not candidates:
|
||||
log_to_json("ERROR", "Main", f"获取官方 API 节点失败: {e}")
|
||||
raise
|
||||
err_code, diag_msg = vpn_utils.diagnose_api_failure(API_URL)
|
||||
full_err_msg = f"获取官方 API 节点失败: {e} | 诊断结果: {diag_msg}"
|
||||
print(f"[错误代码 {err_code}] {full_err_msg}", flush=True)
|
||||
log_to_json("ERROR", "Main", f"[错误代码 {err_code}] {full_err_msg}")
|
||||
set_state(
|
||||
last_fetch_status="error",
|
||||
last_fetch_error_code=err_code,
|
||||
last_fetch_message=diag_msg
|
||||
)
|
||||
raise RuntimeError(diag_msg) from e
|
||||
|
||||
set_state(
|
||||
last_fetch_at=time.time(),
|
||||
@@ -468,9 +476,9 @@ def run_openvpn_until_ready(config_file: str, keep_alive: bool, route_nopull: bo
|
||||
cwd=str(ROOT_DIR),
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return False, "openvpn command not found", None
|
||||
return False, "[错误代码 2001] [ERR_OVPN_CMD_NOT_FOUND] 未找到 openvpn 命令。原因: 系统未安装 openvpn,或 PATH 环境变量不正确。", None
|
||||
except OSError as exc:
|
||||
return False, f"openvpn start failed: {exc}", None
|
||||
return False, f"[错误代码 2002] [ERR_OVPN_START_FAILED] openvpn 启动失败: {exc}。原因: 系统权限不足或配置冲突。", None
|
||||
|
||||
lines: queue.Queue[str | None] = queue.Queue()
|
||||
startup_done = [False]
|
||||
@@ -521,8 +529,9 @@ def run_openvpn_until_ready(config_file: str, keep_alive: bool, route_nopull: bo
|
||||
else:
|
||||
message = f"OpenVPN timeout after {limit}s."
|
||||
|
||||
if not ok and tail:
|
||||
message = tail[-1][-220:]
|
||||
if not ok:
|
||||
err_code, diag_msg = vpn_utils.diagnose_openvpn_failure(tail)
|
||||
message = f"[错误代码 {err_code}] {diag_msg} (原始日志尾部: {tail[-1][-100:] if tail else '无'})"
|
||||
startup_done[0] = True
|
||||
if not keep_alive or not ok:
|
||||
stop_process(process)
|
||||
@@ -975,7 +984,11 @@ def maintain_valid_nodes(force: bool = False) -> str:
|
||||
candidates = fetch_candidates()
|
||||
except Exception as exc:
|
||||
vpn_utils.check_and_fix_dns()
|
||||
set_state(last_fetch_at=time.time(), last_fetch_status="error", last_fetch_message=str(exc))
|
||||
diag_msg = str(exc)
|
||||
if not any(token in diag_msg for token in ["[ERR_", "错误代码"]):
|
||||
err_code, raw_diag = vpn_utils.diagnose_api_failure(API_URL)
|
||||
diag_msg = f"[错误代码 {err_code}] 获取节点失败: {exc} | 诊断结果: {raw_diag}"
|
||||
set_state(last_fetch_at=time.time(), last_fetch_status="error", last_fetch_message=diag_msg)
|
||||
candidates = []
|
||||
|
||||
if not candidates:
|
||||
@@ -2709,11 +2722,7 @@ function render(){
|
||||
pBadge.className = "badge unavailable";
|
||||
pBadge.textContent = "不可用";
|
||||
pIpVal.textContent = "-";
|
||||
if (state.last_check_message) {
|
||||
pLatVal.innerHTML = `<span style="color: var(--text-secondary); font-size: 12px;">${esc(state.last_check_message)}</span>`;
|
||||
} else {
|
||||
pLatVal.innerHTML = `<span class="latency-val latency-poor" style="margin-left:8px; font-size:11px;" title="${esc(state.proxy_error)}">${esc(state.proxy_error || "连接失败")}</span>`;
|
||||
}
|
||||
pLatVal.innerHTML = `<span class="latency-val latency-poor" style="margin-left:8px; font-size:11px; max-width: 450px; display: inline-block; white-space: normal; line-height: 1.4; text-align: left;" title="${esc(state.proxy_error)}">${esc(state.proxy_error || "连接失败")}</span>`;
|
||||
}
|
||||
} else {
|
||||
pBadge.className = "badge not_checked";
|
||||
@@ -3303,9 +3312,11 @@ def check_proxy_health() -> dict[str, Any]:
|
||||
try:
|
||||
s.connect(("127.0.0.1", LOCAL_PROXY_PORT))
|
||||
except Exception as e:
|
||||
diag = vpn_utils.diagnose_local_obstructions(LOCAL_PROXY_PORT)
|
||||
diag_msg = diag[1] if diag else f"端口 {LOCAL_PROXY_PORT} 连接失败,原因: {e}"
|
||||
return {
|
||||
"ok": False,
|
||||
"error": f"代理服务未运行 (端口 {LOCAL_PROXY_PORT} 连接失败,原因: {e})"
|
||||
"error": f"代理服务未运行 ({diag_msg})"
|
||||
}
|
||||
finally:
|
||||
try:
|
||||
@@ -3318,7 +3329,7 @@ def check_proxy_health() -> dict[str, Any]:
|
||||
if sys.platform.startswith("linux") and not tun_path.exists():
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "VPN 虚拟网卡 (tun0) 未启用,请确保当前已成功连接 VPN 节点"
|
||||
"error": "[错误代码 3004] [ERR_ROUTE_DEV_NOT_FOUND] VPN 虚拟网卡 (tun0) 未启用,请确保当前已成功连接 VPN 节点"
|
||||
}
|
||||
|
||||
# 3. 使用 curl 通过本地 SOCKS5 代理接口测试 IP 与实际延迟
|
||||
@@ -3353,7 +3364,12 @@ def check_proxy_health() -> dict[str, Any]:
|
||||
result = _curl_check_ip("http://api.ipify.org")
|
||||
if result:
|
||||
return result
|
||||
return {"ok": False, "error": "出口连接测试失败 (ip.sb 和 api.ipify.org 均无法连通)"}
|
||||
|
||||
diag = vpn_utils.diagnose_local_obstructions(LOCAL_PROXY_PORT)
|
||||
if diag:
|
||||
return {"ok": False, "error": f"出口连接测试失败 | 本机诊断结果: {diag[1]}"}
|
||||
|
||||
return {"ok": False, "error": "出口连接测试失败 (ip.sb 和 api.ipify.org 均无法连通,可能是节点已失效或 VPS 防火墙限制了 UDP/TCP 出站端口)"}
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": f"出口连接测试异常: {e}"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user