mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-08 17:26:46 +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("出口 IP (出站)", proxy_ip))
|
||||||
print_line(format_line("本地代理延迟", f"{proxy_latency} ms" if proxy_latency else "检测中..."))
|
print_line(format_line("本地代理延迟", f"{proxy_latency} ms" if proxy_latency else "检测中..."))
|
||||||
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:
|
else:
|
||||||
print_line(format_line("节点状态", "无活动连接"))
|
print_line(format_line("节点状态", "无活动连接"))
|
||||||
print_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)
|
sock.settimeout(timeout)
|
||||||
try:
|
try:
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"tun0")
|
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
|
return None
|
||||||
sock.sendto(packet, (dns_server, 53))
|
sock.sendto(packet, (dns_server, 53))
|
||||||
resp, _ = sock.recvfrom(2048)
|
resp, _ = sock.recvfrom(2048)
|
||||||
@@ -131,6 +135,10 @@ def create_connection(address: tuple[str, int], timeout: float = 20) -> socket.s
|
|||||||
return sock
|
return sock
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
err = 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:
|
if sock is not None:
|
||||||
sock.close()
|
sock.close()
|
||||||
if err is not None:
|
if err is not None:
|
||||||
@@ -255,7 +263,10 @@ def start_proxy_server(host: str, port: int) -> None:
|
|||||||
server.listen(256)
|
server.listen(256)
|
||||||
print(f"HTTP/SOCKS5 proxy listening on {host}:{port}", flush=True)
|
print(f"HTTP/SOCKS5 proxy listening on {host}:{port}", flush=True)
|
||||||
except Exception as e:
|
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
|
return
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
+170
@@ -420,3 +420,173 @@ def enrich_ip_info(nodes: list[dict[str, Any]]) -> None:
|
|||||||
node["location"] = cached.get("location", "")
|
node["location"] = cached.get("location", "")
|
||||||
node["ip_type"] = cached.get("ip_type", "")
|
node["ip_type"] = cached.get("ip_type", "")
|
||||||
node["quality"] = cached.get("quality", "")
|
node["quality"] = cached.get("quality", "")
|
||||||
|
|
||||||
|
|
||||||
|
def diagnose_api_failure(api_url: str = "https://www.vpngate.net/api/iphone/") -> tuple[int, str]:
|
||||||
|
try:
|
||||||
|
parsed = urllib.parse.urlsplit(api_url)
|
||||||
|
domain = parsed.hostname or "www.vpngate.net"
|
||||||
|
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
||||||
|
except Exception:
|
||||||
|
domain = "www.vpngate.net"
|
||||||
|
port = 443
|
||||||
|
|
||||||
|
# 1. 检查本地 DNS 解析是否完全失效
|
||||||
|
dns_ok = False
|
||||||
|
for test_domain in ["api.ipify.org", "dns.google", "one.one.one.one"]:
|
||||||
|
try:
|
||||||
|
socket.gethostbyname(test_domain)
|
||||||
|
dns_ok = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. 检查是否能解析 API 域名
|
||||||
|
api_dns_ok = False
|
||||||
|
api_ip = None
|
||||||
|
try:
|
||||||
|
api_ip = socket.gethostbyname(domain)
|
||||||
|
api_dns_ok = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not api_dns_ok:
|
||||||
|
if not dns_ok:
|
||||||
|
return 1006, "[ERR_LOCAL_DNS_BROKEN] 本地 DNS 解析器完全失效。原因: 无法解析任何外部域名,请检查系统 DNS 配置(如 /etc/resolv.conf)及外网连接。"
|
||||||
|
else:
|
||||||
|
return 1007, f"[ERR_API_DOMAIN_BLOCKED] 解析 API 域名 {domain} 失败。原因: 其他外部域名解析正常,确认该官方 API 域名遭 DNS 污染或本地防火墙拦截。"
|
||||||
|
|
||||||
|
# 3. 检查 TCP 连接 API 域名
|
||||||
|
api_conn_ok = False
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(4)
|
||||||
|
try:
|
||||||
|
s.connect((api_ip, port))
|
||||||
|
api_conn_ok = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not api_conn_ok:
|
||||||
|
ext_conn_ok = False
|
||||||
|
for test_ip, test_port in [("8.8.8.8", 53), ("1.1.1.1", 53)]:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(3)
|
||||||
|
try:
|
||||||
|
s.connect((test_ip, test_port))
|
||||||
|
ext_conn_ok = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if ext_conn_ok:
|
||||||
|
return 1008, f"[ERR_API_IP_BLOCKED_OR_DOWN] 连接 API 服务器失败。原因: 外部网络连接通畅,但无法建立到 {domain} ({api_ip}:{port}) 的连接,可能是由于官方 IP 遭 GFW/防火墙 IP 阻断封锁或官方服务器宕机。"
|
||||||
|
else:
|
||||||
|
return 1009, "[ERR_VPS_OUTBOUND_BLOCKED] VPS 完全断网。原因: 任何外部测试连接均失败,请检查 VPS 网卡和宿主机连接。"
|
||||||
|
|
||||||
|
return 1010, f"[ERR_API_TLS_INTERFERENCE] HTTPS/TLS 握手被干扰。原因: 可以建立 TCP 连接但请求超时,通常是由于防火墙通过 SNI 阻断了 TLS 握手流。"
|
||||||
|
|
||||||
|
|
||||||
|
def diagnose_openvpn_failure(log_tail: list[str]) -> tuple[int, str]:
|
||||||
|
joined_log = "\n".join(log_tail).lower()
|
||||||
|
|
||||||
|
if "command not found" in joined_log or "no such file or directory" in joined_log:
|
||||||
|
return 2001, "[ERR_OVPN_CMD_NOT_FOUND] 未找到 openvpn 命令。原因: 系统中未安装 OpenVPN 软件,或环境变量 PATH 不正确。"
|
||||||
|
|
||||||
|
if "cannot allocate tun" in joined_log or "cannot open tun/tap dev" in joined_log or "cannot ioctl" in joined_log or "cannot allocate tun/tap dev" in joined_log:
|
||||||
|
return 2009, "[ERR_OVPN_TUN_NOT_AVAILABLE] 无法创建虚拟网卡 (TUN 设备)。原因: 缺少 tun 内核模块,或当前容器(如 LXC/OpenVZ/Docker)未被宿主机授予网卡创建权限。请在 VPS 面板中启用 TUN 或联系服务商。"
|
||||||
|
|
||||||
|
if "auth_failed" in joined_log or "authentication failed" in joined_log:
|
||||||
|
return 2005, "[ERR_OVPN_AUTH_FAILED] OpenVPN 身份验证失败。原因: 节点配置的用户名密码不正确,或者该免费节点已失效/限制连接。"
|
||||||
|
|
||||||
|
if "cannot resolve host address" in joined_log or "resolve: host name" in joined_log:
|
||||||
|
return 2003, "[ERR_OVPN_DNS_RESOLVE] 节点服务器域名解析失败。原因: 本地 DNS 解析异常,或者节点域名已失效。"
|
||||||
|
|
||||||
|
if "tls error: tls key negotiation failed" in joined_log or "tls error: tls handshake failed" in joined_log:
|
||||||
|
return 2006, "[ERR_OVPN_TLS_BLOCKED] TLS 握手超时/失败。原因: 可能是由于物理链路极差导致握手包丢失,或者受 VPS 防火墙规则/网络监管(如 GFW)深度包检测拦截了 OpenVPN 协议流量。"
|
||||||
|
|
||||||
|
if "connection timed out" in joined_log or "timeout" in joined_log:
|
||||||
|
return 2004, "[ERR_OVPN_NODE_UNREACHABLE] 节点连接超时。原因: 远程节点已关机、VPS 本身出站流量被本地防火墙拦截,或者目的 IP:端口遭 ISP/GFW 屏蔽拦截。"
|
||||||
|
if "connection refused" in joined_log:
|
||||||
|
return 2004, "[ERR_OVPN_NODE_UNREACHABLE] 节点连接被拒绝。原因: 目的服务器未在指定端口监听,或者主动拒绝了连接。"
|
||||||
|
|
||||||
|
if "options error" in joined_log:
|
||||||
|
return 2007, "[ERR_OVPN_ROUTE_NOPULL] 获取/解析 PUSH 配置参数冲突。原因: 某些推送选项在当前版本的客户端或配置环境中不可用。"
|
||||||
|
|
||||||
|
return 2010, "[ERR_OVPN_UNKNOWN] OpenVPN 其他运行时异常。原因: 连接握手期间发生其他协议错误,详细信息请查看日志尾部。"
|
||||||
|
|
||||||
|
|
||||||
|
def diagnose_local_obstructions(proxy_port: int = 7928) -> tuple[int, str] | None:
|
||||||
|
import sys
|
||||||
|
# 1. 检查端口是否被占用
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
try:
|
||||||
|
s.bind(("127.0.0.1", proxy_port))
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 98 or e.errno == 10048 or "already in use" in str(e).lower():
|
||||||
|
return 3005, f"[ERR_PORT_IN_USE] 本地代理端口 {proxy_port} 被占用。原因: 其他进程已抢占该端口,导致本系统代理网关启动失败。请运行 'lsof -i :{proxy_port}' 检查占用进程。"
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
# 2. 检查 IPv4 转发是否开启
|
||||||
|
ip_forward_path = Path("/proc/sys/net/ipv4/ip_forward")
|
||||||
|
if ip_forward_path.exists():
|
||||||
|
try:
|
||||||
|
val = ip_forward_path.read_text(encoding="utf-8").strip()
|
||||||
|
if val == "0":
|
||||||
|
return 3001, "[ERR_ROUTE_FORWARD_DISABLED] 系统未开启 IPv4 流量转发。原因: /proc/sys/net/ipv4/ip_forward 值为 0,会导致 VPN 隧道内的流量无法进行正常的网络转发。"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 3. 检查本机防火墙策略
|
||||||
|
# 检查 UFW
|
||||||
|
try:
|
||||||
|
res = subprocess.run(["ufw", "status"], capture_output=True, text=True, timeout=2)
|
||||||
|
if res.returncode == 0 and "Status: active" in res.stdout:
|
||||||
|
if str(proxy_port) not in res.stdout:
|
||||||
|
return 3007, f"[ERR_FIREWALL_BLOCKING_FORWARD] 本机 UFW 防火墙处于激活状态,但未在规则中允许代理端口 {proxy_port}。这可能会阻断客户端的连接。"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 检查 Firewalld
|
||||||
|
try:
|
||||||
|
res = subprocess.run(["systemctl", "is-active", "firewalld"], capture_output=True, text=True, timeout=2)
|
||||||
|
if res.returncode == 0 and res.stdout.strip() == "active":
|
||||||
|
return 3007, "[ERR_FIREWALL_BLOCKING_FORWARD] 本机 Firewalld 防火墙正在运行。请确保您已将代理端口及 VPN 网卡(tun0)加入信任区域以避免流量被拦截。"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 检查 iptables 默认策略
|
||||||
|
try:
|
||||||
|
res = subprocess.run(["iptables", "-S"], capture_output=True, text=True, timeout=2)
|
||||||
|
if res.returncode == 0:
|
||||||
|
lines = res.stdout.splitlines()
|
||||||
|
has_output_drop = False
|
||||||
|
has_forward_drop = False
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("-P OUTPUT DROP"):
|
||||||
|
has_output_drop = True
|
||||||
|
elif line.startswith("-P FORWARD DROP"):
|
||||||
|
has_forward_drop = True
|
||||||
|
|
||||||
|
if has_output_drop:
|
||||||
|
return 3007, "[ERR_FIREWALL_BLOCKING_FORWARD] 本机 iptables OUTPUT 默认策略被设为 DROP。这会导致 VPS 出站数据包被静默丢弃,从而彻底阻碍网关运行。"
|
||||||
|
if has_forward_drop:
|
||||||
|
return 3007, "[ERR_FIREWALL_BLOCKING_FORWARD] 本机 iptables FORWARD 默认策略被设为 DROP。且未配置相应的转发规则,这通常会拦截 VPN 网卡的流量穿透。"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None␍
|
||||||
+31
-15
@@ -333,8 +333,16 @@ def fetch_candidates() -> list[dict[str, Any]]:
|
|||||||
print(f"[fetch_candidates] Fetch {i+1} failed: {e}", flush=True)
|
print(f"[fetch_candidates] Fetch {i+1} failed: {e}", flush=True)
|
||||||
log_to_json("WARNING", "Main", f"第 {i+1} 次拉取 API 节点失败: {e}")
|
log_to_json("WARNING", "Main", f"第 {i+1} 次拉取 API 节点失败: {e}")
|
||||||
if i == max_attempts - 1 and not candidates:
|
if i == max_attempts - 1 and not candidates:
|
||||||
log_to_json("ERROR", "Main", f"获取官方 API 节点失败: {e}")
|
err_code, diag_msg = vpn_utils.diagnose_api_failure(API_URL)
|
||||||
raise
|
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(
|
set_state(
|
||||||
last_fetch_at=time.time(),
|
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),
|
cwd=str(ROOT_DIR),
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
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:
|
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()
|
lines: queue.Queue[str | None] = queue.Queue()
|
||||||
startup_done = [False]
|
startup_done = [False]
|
||||||
@@ -521,8 +529,9 @@ def run_openvpn_until_ready(config_file: str, keep_alive: bool, route_nopull: bo
|
|||||||
else:
|
else:
|
||||||
message = f"OpenVPN timeout after {limit}s."
|
message = f"OpenVPN timeout after {limit}s."
|
||||||
|
|
||||||
if not ok and tail:
|
if not ok:
|
||||||
message = tail[-1][-220:]
|
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
|
startup_done[0] = True
|
||||||
if not keep_alive or not ok:
|
if not keep_alive or not ok:
|
||||||
stop_process(process)
|
stop_process(process)
|
||||||
@@ -975,7 +984,11 @@ def maintain_valid_nodes(force: bool = False) -> str:
|
|||||||
candidates = fetch_candidates()
|
candidates = fetch_candidates()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
vpn_utils.check_and_fix_dns()
|
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 = []
|
candidates = []
|
||||||
|
|
||||||
if not candidates:
|
if not candidates:
|
||||||
@@ -2709,11 +2722,7 @@ function render(){
|
|||||||
pBadge.className = "badge unavailable";
|
pBadge.className = "badge unavailable";
|
||||||
pBadge.textContent = "不可用";
|
pBadge.textContent = "不可用";
|
||||||
pIpVal.textContent = "-";
|
pIpVal.textContent = "-";
|
||||||
if (state.last_check_message) {
|
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>`;
|
||||||
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>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pBadge.className = "badge not_checked";
|
pBadge.className = "badge not_checked";
|
||||||
@@ -3303,9 +3312,11 @@ def check_proxy_health() -> dict[str, Any]:
|
|||||||
try:
|
try:
|
||||||
s.connect(("127.0.0.1", LOCAL_PROXY_PORT))
|
s.connect(("127.0.0.1", LOCAL_PROXY_PORT))
|
||||||
except Exception as e:
|
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 {
|
return {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
"error": f"代理服务未运行 (端口 {LOCAL_PROXY_PORT} 连接失败,原因: {e})"
|
"error": f"代理服务未运行 ({diag_msg})"
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
@@ -3318,7 +3329,7 @@ def check_proxy_health() -> dict[str, Any]:
|
|||||||
if sys.platform.startswith("linux") and not tun_path.exists():
|
if sys.platform.startswith("linux") and not tun_path.exists():
|
||||||
return {
|
return {
|
||||||
"ok": False,
|
"ok": False,
|
||||||
"error": "VPN 虚拟网卡 (tun0) 未启用,请确保当前已成功连接 VPN 节点"
|
"error": "[错误代码 3004] [ERR_ROUTE_DEV_NOT_FOUND] VPN 虚拟网卡 (tun0) 未启用,请确保当前已成功连接 VPN 节点"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 3. 使用 curl 通过本地 SOCKS5 代理接口测试 IP 与实际延迟
|
# 3. 使用 curl 通过本地 SOCKS5 代理接口测试 IP 与实际延迟
|
||||||
@@ -3353,7 +3364,12 @@ def check_proxy_health() -> dict[str, Any]:
|
|||||||
result = _curl_check_ip("http://api.ipify.org")
|
result = _curl_check_ip("http://api.ipify.org")
|
||||||
if result:
|
if result:
|
||||||
return 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:
|
except Exception as e:
|
||||||
return {"ok": False, "error": f"出口连接测试异常: {e}"}
|
return {"ok": False, "error": f"出口连接测试异常: {e}"}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user