mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-05 07:36:42 +08:00
fix: add recursive depth limit to auto_switch_node, improve connection status tracking, and implement UUID-based session management
This commit is contained in:
+98
-78
@@ -306,6 +306,9 @@ def format_line(label, value, target_width=26):
|
||||
padding = " " * max(0, target_width - w)
|
||||
return f"{prefix}{label}{padding}: {value}"
|
||||
|
||||
def print_line(text=""):
|
||||
print(f"{text}\033[K")
|
||||
|
||||
def print_status():
|
||||
cfg = load_ui_cfg()
|
||||
ui_port = cfg.get("port", 8787)
|
||||
@@ -336,36 +339,36 @@ def print_status():
|
||||
gateway_status = f"{green}[已激活]{reset}" if gateway_ok else f"{red}[未启动]{reset}"
|
||||
openvpn_status = f"{green}[已连接]{reset}" if openvpn_ok else f"{red}[未连接]{reset}"
|
||||
|
||||
print("=======================================================")
|
||||
print(f" {bold}AimiliVPN 管理终端 v2.0{reset} ")
|
||||
print("=======================================================")
|
||||
print("【核心服务状态】")
|
||||
print(format_line("代理网关 (Port 7928)", gateway_status))
|
||||
print(format_line(f"管理后台 (Port {ui_port})", backend_status))
|
||||
print(format_line("连接核心 (OpenVPN)", openvpn_status))
|
||||
print_line("=======================================================")
|
||||
print_line(f" {bold}AimiliVPN 管理终端 v2.0{reset} ")
|
||||
print_line("=======================================================")
|
||||
print_line("【核心服务状态】")
|
||||
print_line(format_line("代理网关 (Port 7928)", gateway_status))
|
||||
print_line(format_line(f"管理后台 (Port {ui_port})", backend_status))
|
||||
print_line(format_line("连接核心 (OpenVPN)", openvpn_status))
|
||||
|
||||
login_ip = "127.0.0.1" if cfg.get("host") == "127.0.0.1" else get_public_ip()
|
||||
print(format_line("网页登录地址", f"{yellow}http://{login_ip}:{ui_port}/{secret_path}/{reset}"))
|
||||
print(format_line("网页管理账号", cfg.get("username", "未配置")))
|
||||
print_line(format_line("网页登录地址", f"{yellow}http://{login_ip}:{ui_port}/{secret_path}/{reset}"))
|
||||
print_line(format_line("网页管理账号", cfg.get("username", "未配置")))
|
||||
curr_pwd = cfg.get("password", "")
|
||||
masked_pwd = curr_pwd if len(curr_pwd) <= 4 else curr_pwd[:3] + "********" + curr_pwd[-2:]
|
||||
print(format_line("网页管理密码", masked_pwd))
|
||||
print()
|
||||
print("【活动节点状态】")
|
||||
print_line(format_line("网页管理密码", masked_pwd))
|
||||
print_line()
|
||||
print_line("【活动节点状态】")
|
||||
if is_connecting:
|
||||
connecting_msg = state.get('last_check_message') or '正在建立加密隧道并验证路由规则...'
|
||||
print(format_line("节点状态", f"{yellow}{connecting_msg}{reset}"))
|
||||
print_line(format_line("节点状态", f"{yellow}{connecting_msg}{reset}"))
|
||||
elif active_ip:
|
||||
print(format_line("节点 IP", active_ip))
|
||||
print(format_line("节点地区", active_loc))
|
||||
print(format_line("节点延迟 (直连测试)", latency))
|
||||
print_line(format_line("节点 IP", active_ip))
|
||||
print_line(format_line("节点地区", active_loc))
|
||||
print_line(format_line("节点延迟 (直连测试)", latency))
|
||||
else:
|
||||
print(format_line("节点状态", "无活动连接"))
|
||||
print()
|
||||
print("【使用方法】")
|
||||
print(f" export http_proxy=socks5://127.0.0.1:7928")
|
||||
print(f" export https_proxy=socks5://127.0.0.1:7928")
|
||||
print("=======================================================")
|
||||
print_line(format_line("节点状态", "无活动连接"))
|
||||
print_line()
|
||||
print_line("【使用方法】")
|
||||
print_line(f" export http_proxy=socks5://127.0.0.1:7928")
|
||||
print_line(f" export https_proxy=socks5://127.0.0.1:7928")
|
||||
print_line("=======================================================")
|
||||
|
||||
def start_service():
|
||||
print("正在启动 AimiliVPN 服务...", flush=True)
|
||||
@@ -669,18 +672,24 @@ def main():
|
||||
elif cmd == "restart":
|
||||
restart_service()
|
||||
elif cmd == "status":
|
||||
print("\033[?1049h\033[?25l\033[H\033[J", end="", flush=True)
|
||||
try:
|
||||
last_state = None
|
||||
while True:
|
||||
current_state = get_status_state()
|
||||
if current_state != last_state:
|
||||
print("\033[H\033[J", end="")
|
||||
print("\033[H", end="")
|
||||
print_status()
|
||||
print("\n提示: 正在实时监控状态,自动更新。按 Ctrl+C 退出...")
|
||||
print_line("\n\033[1;33m提示: 正在实时监控状态,自动更新。按任意键或 Ctrl+C 退出...\033[0m")
|
||||
print("\033[J", end="", flush=True)
|
||||
last_state = current_state
|
||||
time.sleep(2.0)
|
||||
key = getch_timeout(1.5)
|
||||
if key is not None:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
print("\033[?1049l\033[?25h", end="", flush=True)
|
||||
elif cmd == "logs":
|
||||
show_logs()
|
||||
elif cmd == "update":
|
||||
@@ -710,63 +719,74 @@ def main():
|
||||
'0': ("退出终端", None)
|
||||
}
|
||||
|
||||
last_state = None
|
||||
while True:
|
||||
current_state = get_status_state()
|
||||
if current_state != last_state:
|
||||
print("\033[H\033[J", end="")
|
||||
print_status()
|
||||
|
||||
bold = "\033[1m"
|
||||
reset = "\033[0m"
|
||||
green = "\033[1;32m"
|
||||
|
||||
print(f"【{bold}终端指令菜单栏{reset}】")
|
||||
for key in sorted(options.keys()):
|
||||
if key == '0':
|
||||
continue
|
||||
name, _ = options[key]
|
||||
print(f" {green}[{key}]{reset} {name}")
|
||||
print(f" {green}[0]{reset} {options['0'][0]}")
|
||||
print("=======================================================")
|
||||
print("请直接输入数字键 [0-9] 快速选择执行:", end="", flush=True)
|
||||
last_state = current_state
|
||||
|
||||
try:
|
||||
key = getch()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
break
|
||||
|
||||
if key == '\x03':
|
||||
print()
|
||||
break
|
||||
|
||||
# '0' 键默认退出终端,并打印换行符以保持 Shell 提示符整洁
|
||||
if key == '0':
|
||||
print()
|
||||
break
|
||||
|
||||
# 回车键 (\r 或 \n) 用于手动刷新当前菜单与连接状态
|
||||
if key in ('\r', '\n', '\x0a', '\x0d'):
|
||||
pass
|
||||
|
||||
# Reset last_state to force redraw after any key input
|
||||
# Enter alternate buffer and hide cursor
|
||||
print("\033[?1049h\033[?25l\033[H\033[J", end="", flush=True)
|
||||
try:
|
||||
last_state = None
|
||||
|
||||
if key in options:
|
||||
name, func = options[key]
|
||||
if func is None:
|
||||
print()
|
||||
while True:
|
||||
current_state = get_status_state()
|
||||
if current_state != last_state:
|
||||
print("\033[H", end="")
|
||||
print_status()
|
||||
|
||||
bold = "\033[1m"
|
||||
reset = "\033[0m"
|
||||
green = "\033[1;32m"
|
||||
|
||||
print_line(f"【{bold}终端指令菜单栏{reset}】")
|
||||
for key in sorted(options.keys()):
|
||||
if key == '0':
|
||||
continue
|
||||
name, _ = options[key]
|
||||
print_line(f" {green}[{key}]{reset} {name}")
|
||||
print_line(f" {green}[0]{reset} {options['0'][0]}")
|
||||
print_line("=======================================================")
|
||||
print("请直接输入数字键 [0-9] 快速选择执行:\033[K", end="", flush=True)
|
||||
print("\033[J", end="", flush=True)
|
||||
last_state = current_state
|
||||
|
||||
try:
|
||||
key = getch_timeout(1.0)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
print("\033[H\033[J", end="")
|
||||
print(f"正在执行: {name}...\n")
|
||||
func()
|
||||
if func in (start_service, stop_service, restart_service):
|
||||
|
||||
if key is None:
|
||||
continue
|
||||
if func in (configure_web, configure_port, configure_credentials, show_logs, update_service):
|
||||
|
||||
if key == '\x03' or key == 'q' or key == 'Q':
|
||||
break
|
||||
|
||||
if key == '0':
|
||||
break
|
||||
|
||||
if key in ('\r', '\n', '\x0a', '\x0d'):
|
||||
last_state = None
|
||||
continue
|
||||
input("\n操作已完成,按回车键返回主菜单...")
|
||||
|
||||
if key in options:
|
||||
name, func = options[key]
|
||||
if func is None:
|
||||
break
|
||||
|
||||
# Temporarily restore normal terminal scrollback and show cursor
|
||||
print("\033[?1049l\033[?25h", end="", flush=True)
|
||||
print(f"正在执行: {name}...\n")
|
||||
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
print(f"执行出错: {e}")
|
||||
|
||||
if func not in (start_service, stop_service, restart_service,
|
||||
configure_web, configure_port, configure_credentials, show_logs, update_service):
|
||||
input("\n操作已完成,按回车键返回主菜单...")
|
||||
|
||||
# Re-enter alternate buffer and hide cursor
|
||||
print("\033[?1049h\033[?25l\033[H\033[J", end="", flush=True)
|
||||
last_state = None
|
||||
finally:
|
||||
# Exit alternate buffer and show cursor on exit
|
||||
print("\033[?1049l\033[?25h", end="", flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+103
-1
@@ -22,8 +22,103 @@ def recv_exact(sock: socket.socket, size: int) -> bytes:
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float = 3.0) -> str | None:
|
||||
try:
|
||||
socket.inet_aton(host)
|
||||
return host
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
import random
|
||||
tx_id = random.getrandbits(16).to_bytes(2, "big")
|
||||
flags = b"\x01\x00"
|
||||
questions = b"\x00\x01"
|
||||
rrs = b"\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
qname = b""
|
||||
for part in host.split("."):
|
||||
if not part:
|
||||
continue
|
||||
part_bytes = part.encode("idna")
|
||||
qname += len(part_bytes).to_bytes(1, "big") + part_bytes
|
||||
qname += b"\x00"
|
||||
|
||||
qtype_qclass = b"\x00\x01\x00\x01"
|
||||
packet = tx_id + flags + questions + rrs + qname + qtype_qclass
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
sock.settimeout(timeout)
|
||||
try:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"tun0")
|
||||
except OSError:
|
||||
return None
|
||||
sock.sendto(packet, (dns_server, 53))
|
||||
resp, _ = sock.recvfrom(2048)
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
if len(resp) < 12:
|
||||
return None
|
||||
if resp[:2] != tx_id:
|
||||
return None
|
||||
|
||||
rcode = resp[3] & 0x0F
|
||||
if rcode != 0:
|
||||
return None
|
||||
|
||||
offset = 12
|
||||
while offset < len(resp):
|
||||
length = resp[offset]
|
||||
if length == 0:
|
||||
offset += 1
|
||||
break
|
||||
elif (length & 0xC0) == 0xC0:
|
||||
offset += 2
|
||||
break
|
||||
else:
|
||||
offset += 1 + length
|
||||
|
||||
offset += 4
|
||||
answers_count = int.from_bytes(resp[6:8], "big")
|
||||
if answers_count == 0:
|
||||
return None
|
||||
|
||||
for _ in range(answers_count):
|
||||
if offset >= len(resp):
|
||||
break
|
||||
while offset < len(resp):
|
||||
length = resp[offset]
|
||||
if length == 0:
|
||||
offset += 1
|
||||
break
|
||||
elif (length & 0xC0) == 0xC0:
|
||||
offset += 2
|
||||
break
|
||||
else:
|
||||
offset += 1 + length
|
||||
if offset + 10 > len(resp):
|
||||
break
|
||||
atype = int.from_bytes(resp[offset : offset + 2], "big")
|
||||
aclass = int.from_bytes(resp[offset + 2 : offset + 4], "big")
|
||||
rdlength = int.from_bytes(resp[offset + 8 : offset + 10], "big")
|
||||
offset += 10
|
||||
if offset + rdlength > len(resp):
|
||||
break
|
||||
if atype == 1 and aclass == 1 and rdlength == 4:
|
||||
ip_bytes = resp[offset : offset + 4]
|
||||
return socket.inet_ntoa(ip_bytes)
|
||||
offset += rdlength
|
||||
return None
|
||||
|
||||
def create_connection(address: tuple[str, int], timeout: float = 20) -> socket.socket:
|
||||
host, port = address
|
||||
resolved_ip = resolve_dns_over_tun0(host)
|
||||
if resolved_ip:
|
||||
host = resolved_ip
|
||||
|
||||
err = None
|
||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
@@ -76,7 +171,14 @@ def socks5_client(client: socket.socket, first_byte: bytes) -> None:
|
||||
client.sendall(b"\x05\x08\x00\x01\x00\x00\x00\x00\x00\x00")
|
||||
return
|
||||
port = int.from_bytes(recv_exact(client, 2), "big")
|
||||
upstream = create_connection((host, port), timeout=20)
|
||||
try:
|
||||
upstream = create_connection((host, port), timeout=20)
|
||||
except Exception:
|
||||
try:
|
||||
client.sendall(b"\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00")
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
client.sendall(b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
|
||||
relay(client, upstream)
|
||||
finally:
|
||||
|
||||
+140
-90
@@ -21,6 +21,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
import concurrent.futures
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
# Force socket to resolve IPv4 only to avoid slow AAAA (IPv6) DNS resolution timeouts (e.g. in WSL)
|
||||
_orig_getaddrinfo = socket.getaddrinfo
|
||||
@@ -56,9 +57,10 @@ STATE_FILE = DATA_DIR / "state.json"
|
||||
AUTH_FILE = DATA_DIR / "vpngate_auth.txt"
|
||||
|
||||
lock = threading.RLock()
|
||||
active_sessions: dict[str, float] = {}
|
||||
active_openvpn_process: subprocess.Popen[str] | None = None
|
||||
active_openvpn_node_id = ""
|
||||
is_connecting = False
|
||||
is_connecting = True
|
||||
last_active_ping_time = 0.0
|
||||
last_active_latency = 0
|
||||
|
||||
@@ -762,7 +764,11 @@ def test_multiple_nodes(node_ids: list[str]) -> list[dict[str, Any]]:
|
||||
|
||||
return list(updated_nodes_map.values())
|
||||
|
||||
def auto_switch_node() -> None:
|
||||
def auto_switch_node(attempt: int = 0) -> None:
|
||||
if attempt >= 3:
|
||||
print("[自动切换] 连续切换失败已达 3 次,停止切换以防止主线程死锁,将在后台重新加载节点...", flush=True)
|
||||
return
|
||||
|
||||
# Find the next best available node
|
||||
with lock:
|
||||
nodes = read_json(NODES_FILE, [])
|
||||
@@ -784,7 +790,7 @@ def auto_switch_node() -> None:
|
||||
err_msg = f"切换到备用节点 {next_node['id']} 失败: {e},将尝试下一个..."
|
||||
print(f"[自动切换] {err_msg}", flush=True)
|
||||
log_to_json("WARNING", "VPN", err_msg)
|
||||
auto_switch_node()
|
||||
auto_switch_node(attempt + 1)
|
||||
else:
|
||||
msg = "没有可用的备选节点,将自动断开并清理当前连接状态,同时在后台异步获取新节点..."
|
||||
print(f"[自动切换] {msg}", flush=True)
|
||||
@@ -885,99 +891,121 @@ def connect_node(node_id: str) -> str:
|
||||
is_connecting = False
|
||||
|
||||
def maintain_valid_nodes(force: bool = False) -> str:
|
||||
global active_openvpn_process, active_openvpn_node_id
|
||||
global active_openvpn_process, active_openvpn_node_id, is_connecting
|
||||
ensure_dirs()
|
||||
if force:
|
||||
with lock:
|
||||
stop_active_openvpn()
|
||||
elif not active_openvpn_running():
|
||||
has_active_id = False
|
||||
with lock:
|
||||
if active_openvpn_node_id:
|
||||
has_active_id = True
|
||||
stop_active_openvpn()
|
||||
if has_active_id:
|
||||
print("[维护线程] 检测到当前 OpenVPN 进程已意外退出,准备自动切换节点", flush=True)
|
||||
auto_switch_node()
|
||||
|
||||
is_connecting = True
|
||||
try:
|
||||
candidates = fetch_candidates()
|
||||
except Exception as exc:
|
||||
vpn_utils.check_and_fix_dns()
|
||||
try:
|
||||
candidates = fetch_candidates()
|
||||
except Exception as exc2:
|
||||
set_state(last_fetch_at=time.time(), last_fetch_status="error", last_fetch_message=str(exc2))
|
||||
candidates = []
|
||||
|
||||
if not candidates:
|
||||
return "没有拉取到新节点"
|
||||
|
||||
with lock:
|
||||
active_node = None
|
||||
if active_openvpn_node_id:
|
||||
current_nodes = read_json(NODES_FILE, [])
|
||||
active_node = next((n for n in current_nodes if n.get("id") == active_openvpn_node_id), None)
|
||||
|
||||
merged: list[dict[str, Any]] = []
|
||||
seen_ids: set[str] = set()
|
||||
|
||||
if active_node:
|
||||
merged.append(active_node)
|
||||
seen_ids.add(active_node["id"])
|
||||
|
||||
for cand in candidates:
|
||||
if cand["id"] not in seen_ids:
|
||||
merged.append(cand)
|
||||
seen_ids.add(cand["id"])
|
||||
|
||||
if len(merged) > 1000:
|
||||
merged = merged[:1000]
|
||||
|
||||
for n in merged:
|
||||
config_path = Path(n["config_file"])
|
||||
if not config_path.exists():
|
||||
try:
|
||||
config_path.write_text(n["config_text"], encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
write_json(NODES_FILE, merged)
|
||||
|
||||
# Test the first 10 non-active nodes from the new list
|
||||
with lock:
|
||||
current_nodes = read_json(NODES_FILE, [])
|
||||
to_test = [n for n in current_nodes if not n.get("active")][:10]
|
||||
to_test_ids = [n["id"] for n in to_test]
|
||||
|
||||
print(f"[维护线程] 正在检测新获取列表的前 10 个节点: {to_test_ids}", flush=True)
|
||||
test_multiple_nodes(to_test_ids)
|
||||
|
||||
with lock:
|
||||
merged = read_json(NODES_FILE, [])
|
||||
if not active_openvpn_running():
|
||||
available_candidates = [n for n in merged if n.get("probe_status") == "available"]
|
||||
if available_candidates:
|
||||
if force:
|
||||
with lock:
|
||||
stop_active_openvpn()
|
||||
elif not active_openvpn_running():
|
||||
has_active_id = False
|
||||
with lock:
|
||||
if active_openvpn_node_id:
|
||||
has_active_id = True
|
||||
stop_active_openvpn()
|
||||
if has_active_id:
|
||||
print("[维护线程] 检测到当前 OpenVPN 进程已意外退出,准备自动切换节点", flush=True)
|
||||
is_connecting = False
|
||||
auto_switch_node()
|
||||
is_connecting = True
|
||||
|
||||
valid_nodes_count = len([n for n in merged if n.get("probe_status") == "available"])
|
||||
message = f"Fetched {len(candidates)} nodes. Tested first 10 nodes."
|
||||
set_state(
|
||||
last_check_at=time.time(),
|
||||
last_check_message=message,
|
||||
active_openvpn_node_id=active_openvpn_node_id,
|
||||
valid_nodes=valid_nodes_count,
|
||||
)
|
||||
return message
|
||||
try:
|
||||
set_state(is_connecting=True, last_check_message="正在拉取最新的免费 VPN 节点列表...")
|
||||
candidates = fetch_candidates()
|
||||
except Exception as exc:
|
||||
vpn_utils.check_and_fix_dns()
|
||||
try:
|
||||
set_state(is_connecting=True, last_check_message="解析失败重试:正在拉取最新的免费 VPN 节点列表...")
|
||||
candidates = fetch_candidates()
|
||||
except Exception as exc2:
|
||||
set_state(last_fetch_at=time.time(), last_fetch_status="error", last_fetch_message=str(exc2))
|
||||
candidates = []
|
||||
|
||||
if not candidates:
|
||||
is_connecting = False
|
||||
return "没有拉取到新节点"
|
||||
|
||||
with lock:
|
||||
active_node = None
|
||||
if active_openvpn_node_id:
|
||||
current_nodes = read_json(NODES_FILE, [])
|
||||
active_node = next((n for n in current_nodes if n.get("id") == active_openvpn_node_id), None)
|
||||
|
||||
merged: list[dict[str, Any]] = []
|
||||
seen_ids: set[str] = set()
|
||||
|
||||
if active_node:
|
||||
merged.append(active_node)
|
||||
seen_ids.add(active_node["id"])
|
||||
|
||||
for cand in candidates:
|
||||
if cand["id"] not in seen_ids:
|
||||
merged.append(cand)
|
||||
seen_ids.add(cand["id"])
|
||||
|
||||
if len(merged) > 1000:
|
||||
merged = merged[:1000]
|
||||
|
||||
for n in merged:
|
||||
config_path = Path(n["config_file"])
|
||||
if not config_path.exists():
|
||||
try:
|
||||
config_path.write_text(n["config_text"], encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
write_json(NODES_FILE, merged)
|
||||
|
||||
# Test the first 10 non-active nodes from the new list
|
||||
with lock:
|
||||
current_nodes = read_json(NODES_FILE, [])
|
||||
to_test = [n for n in current_nodes if not n.get("active")][:10]
|
||||
to_test_ids = [n["id"] for n in to_test]
|
||||
|
||||
print(f"[维护线程] 正在检测新获取列表的前 10 个节点: {to_test_ids}", flush=True)
|
||||
set_state(is_connecting=True, last_check_message="正在并发检测筛选可用节点,这可能需要 5-30 秒...")
|
||||
test_multiple_nodes(to_test_ids)
|
||||
|
||||
is_connecting = False
|
||||
|
||||
with lock:
|
||||
merged = read_json(NODES_FILE, [])
|
||||
if not active_openvpn_running():
|
||||
available_candidates = [n for n in merged if n.get("probe_status") == "available"]
|
||||
if available_candidates:
|
||||
auto_switch_node()
|
||||
|
||||
valid_nodes_count = len([n for n in merged if n.get("probe_status") == "available"])
|
||||
message = f"Fetched {len(candidates)} nodes. Tested first 10 nodes."
|
||||
set_state(
|
||||
last_check_at=time.time(),
|
||||
last_check_message=message,
|
||||
active_openvpn_node_id=active_openvpn_node_id,
|
||||
valid_nodes=valid_nodes_count,
|
||||
)
|
||||
return message
|
||||
except Exception as e:
|
||||
is_connecting = False
|
||||
raise e
|
||||
|
||||
|
||||
def collector_loop() -> None:
|
||||
while True:
|
||||
success = False
|
||||
try:
|
||||
maintain_valid_nodes(force=False)
|
||||
res = maintain_valid_nodes(force=False)
|
||||
if "没有拉取到新节点" not in res:
|
||||
success = True
|
||||
except Exception as exc:
|
||||
set_state(last_check_at=time.time(), last_check_message=f"check error: {exc}")
|
||||
time.sleep(CHECK_INTERVAL_SECONDS)
|
||||
|
||||
if not active_openvpn_running() and not success:
|
||||
sleep_time = 30
|
||||
else:
|
||||
sleep_time = CHECK_INTERVAL_SECONDS
|
||||
|
||||
time.sleep(sleep_time)
|
||||
|
||||
LOGIN_HTML = r"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
@@ -3177,7 +3205,6 @@ class Handler(BaseHTTPRequestHandler):
|
||||
def is_authorized(self) -> bool:
|
||||
ui_cfg = load_ui_config()
|
||||
pwd = ui_cfg.get("password")
|
||||
uname = ui_cfg.get("username", "admin")
|
||||
if not pwd:
|
||||
return True
|
||||
|
||||
@@ -3190,8 +3217,15 @@ class Handler(BaseHTTPRequestHandler):
|
||||
k, v = item.split("=", 1)
|
||||
cookies[k.strip()] = v.strip()
|
||||
|
||||
expected_token = get_session_token(pwd, uname)
|
||||
return cookies.get("session") == expected_token
|
||||
session_token = cookies.get("session")
|
||||
if not session_token:
|
||||
return False
|
||||
|
||||
with lock:
|
||||
exp_time = active_sessions.get(session_token)
|
||||
if exp_time is not None and exp_time > time.time():
|
||||
return True
|
||||
return False
|
||||
|
||||
def validate_path(self) -> str:
|
||||
secret_path = self.get_secret_path()
|
||||
@@ -3299,7 +3333,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
expected_uname = ui_cfg.get("username", "admin")
|
||||
|
||||
if expected_pwd and input_pwd == expected_pwd and input_uname == expected_uname:
|
||||
token = get_session_token(expected_pwd, expected_uname)
|
||||
token = uuid.uuid4().hex
|
||||
with lock:
|
||||
active_sessions[token] = time.time() + 30 * 24 * 3600
|
||||
self.send_response(HTTPStatus.OK)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
secret_path = self.get_secret_path()
|
||||
@@ -3315,6 +3351,18 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if effective_path == "/api/logout":
|
||||
try:
|
||||
cookie_header = self.headers.get("Cookie", "")
|
||||
cookies = {}
|
||||
if cookie_header:
|
||||
for item in cookie_header.split(";"):
|
||||
item = item.strip()
|
||||
if "=" in item:
|
||||
k, v = item.split("=", 1)
|
||||
cookies[k.strip()] = v.strip()
|
||||
session_token = cookies.get("session")
|
||||
if session_token:
|
||||
with lock:
|
||||
active_sessions.pop(session_token, None)
|
||||
secret_path = self.get_secret_path()
|
||||
cookie_path = f"/{secret_path}/" if secret_path else "/"
|
||||
self.send_response(HTTPStatus.OK)
|
||||
@@ -3502,7 +3550,9 @@ def main() -> None:
|
||||
"local_proxy": f"http://{LOCAL_PROXY_HOST}:{LOCAL_PROXY_PORT}",
|
||||
"active_openvpn_node_id": "",
|
||||
"last_fetch_status": "starting",
|
||||
"last_check_message": "service starting",
|
||||
"last_check_message": "服务已启动,正在初始化网络并获取候选 VPN 节点...",
|
||||
"is_connecting": True,
|
||||
"active_node_latency": "正在准备",
|
||||
"blacklisted_nodes": 0,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user