第 11 章:系统代理、PAC、TUN 与特权边界
核心在运行不代表系统流量会经过它。应用还要决定全局代理或 PAC、选择实际端口、维护代理守卫,并在核心切换和退出时恢复操作系统。这里最危险的不是设置失败,而是旧异步任务把新会话的代理状态改回去。
一、三条流量接入路径
1.1 全局系统代理
把 HTTP/HTTPS/SOCKS 指向 127.0.0.1:mixed-port,并设置 bypass。适合大多数桌面应用,但不覆盖忽略系统代理的进程。
1.2 PAC
操作系统从嵌入服务器读取 FindProxyForURL,可按域名选择 DIRECT/PROXY。PAC endpoint 只在核心实际运行时可用。
1.3 TUN
通过虚拟网卡接管更广泛流量,需要管理员权限或特权 Service,并与 DNS/fake-ip 配置紧密耦合。
这三者不是互斥的技术实现,而是不同控制范围和权限成本。
二、端口有 desired 与 effective 两种答案
MixedPort 明确区分:
2.1 desired()
优先用户在 Verge 中选择的端口,否则取 Application Merge Config,最后落默认 7897。它在核心启动前就能回答,适合生成配置和启动决策。
2.2 effective()
先问当前 Mihomo base config 的实际端口;读取失败或核心报告 0 时回退 desired。适合用户触发的“把客户端指向正在服务的端口”。
启动 fallback 或手工核心配置可能使两者暂时不同。把它们合成一个 helper,会让配置写入和运行时访问至少有一方拿错答案。
三、系统代理的后端路由
proxy_backend_route(is_macos, running_mode):
- macOS + Service → 通过 Service 修改系统代理;
- 其他组合 → 应用本地
Sysopt修改。
macOS Service 路径这样设计,是因为特权 helper 已持有系统网络控制能力,并能将核心启动与代理修改放在同一 owner session 中。其他平台沿用本地实现。
前端完全不知道这条差异,只调用 Verge 配置 patch。
四、Service 代理配置主动收窄输入
构造 MacosProxyConfig 时:
- host 强制为
127.0.0.1,忽略可疑的自定义远端 host; - mixed port/PAC port 不得为 0;
- bypass 拒绝 NUL;
- bypass 最大 8192 字节并按 UTF-8 边界截断;
- 默认 bypass 包含 loopback、私网和
.local。
这是特权边界的典型原则:传给 Service 的数据不是把 UI 配置原样序列化,而是由非特权侧先规范化成窄、可验证的命令。
五、PAC availability 从 RunningMode 派生
RunStateEnv 接受 set_pac_available(bool):
core_started(Service/Sidecar)→ true;core_stopped()→ false;core_starting()→ 临时 false;core_start_settled()→ 再按 mode 推导。
为什么交接时先关 PAC?操作系统可能在旧核心刚停、新核心未绑定之间请求脚本,拿到的 PAC 会把流量指向空端口。短暂返回不可用比发放错误路由更安全。
六、Proxy Guard:周期修复系统代理
用户或其他软件可能改写系统代理。开启 guard 后,后台按 proxy_guard_duration 周期重新应用期望配置。
普通循环并不安全:
旧 guard 发请求
↓ 正在 await Service
用户关闭系统代理 / 核心切换 owner
↓
旧请求晚到并重新打开代理项目使用 ServiceProxyOperations:
guard_generation:最终操作先 invalidate 旧 guard;operation_lock:周期请求与最终 apply/clear 串行;- captured
OwnerSessionProof:旧 guard 不得采用新的 owner proof; - 每次 RPC 前检查 generation、mode 和 active proof。
七、为什么 invalidate 还不够,还要 drain
generation 能阻止“尚未发出”的旧请求,但一个请求可能已经进入 Service。最终 clear 若不等待它结束,顺序可能是:
clear 完成 → 旧 guard RPC 完成 → 代理又被打开run_final_service_operation() 先 invalidate,再获取同一 operation lock,等待在途 guard 完成,最后执行 clear/apply。最终操作因此一定排在所有旧请求之后。
这是异步资源控制常见的 cancel-and-drain 协议:取消未来工作,并排空已经开始的工作。
八、Owner proof 防止跨会话控制
Guard 捕获的 proof 必须与 active_service_session() 完全相等。即使 RunningMode 仍是 Service、generation 也恰好未变,只要 Service 已被另一个应用会话接管,旧 guard 就退出。
这比只比较 PID 强:服务进程可以长期不变,内部托管的核心和 owner session 已经换代。
九、TUN 能力与配置派生
打开 TUN 会影响多个层面:
- Verge 保存用户意图;
- RunState 判断是否具备权限;
- enhance 写
tun.enable; - 必要时补 fake-ip DNS;
- Linux 可能必须重启核心;
- tray/icon/UI 同步;
- 失去能力时 session suppress 或持久关闭。
因此 TUN 开关不能被实现成 patchClashConfig({ tun: { enable } })。那会绕过权限、DNS、服务对话和一致性恢复。
十、Session suppress 与持久关闭
当用户选择“本次继续 Sidecar”时,系统不一定要永久改掉 TUN 设置。TUN_SESSION_SUPPRESSED 表示:
- 配置文件仍记得用户希望 TUN;
- 本次会话 enhance 时强制视为关闭;
- 服务修复后可恢复意图。
另一路 disable_tun_and_persist() 则明确写入 enable_tun_mode=false。
短期能力缺失和长期用户选择被分开建模,避免一次服务故障永久改变偏好。
十一、退出与异常退出恢复
正常退出会:
- 停止 proxy guard;
- 清系统代理;
- 停止核心;
- macOS 恢复公共 DNS;
- 保存配置。
但 Windows session ending、SIGTERM 或 UI 崩溃可能绕过正常路径。项目同时注册系统 signal handler、Tauri Exit/ExitRequested,并使用 shutdown latch 避免重复清理。
重复清理也危险:两个 stop/clear 并发可能互相覆盖状态。latch 的 Canceled outcome 会释放,允许用户取消后再次退出;Committed 则永久挡住重复信号。
十二、系统集成的安全原则
- 指向核心时使用 effective,生成配置时使用 desired。
- 特权命令的数据面要主动收窄和规范化。
- 后台修复任务必须有代际、所有权和 drain。
- 短期降级不等同于永久改偏好。
- 恢复系统状态是退出协议的一部分,不是附加清理。
本章源码索引
src-tauri/src/config/mixed_port.rs::MixedPort:desired/effectivesrc-tauri/src/core/proxy_control.rs:系统代理路由、guard 与 cancel-and-drainsrc-tauri/src/core/sysopt.rs:本地系统代理与 guardsrc-tauri/src/core/service.rs::set_system_proxy_by_service:Service 路径src-tauri/src/utils/server.rs:PAC endpoint 与 availabilitysrc-tauri/src/enhance/tun.rs:TUN/DNS 配置派生src-tauri/src/feat/tun.rs:TUN reconciliationcrates/clash-verge-signal/src/:跨平台退出信号与 ShutdownLatch