Pangolin 客户端 · 断网保护设计依据与现状 · 2026-06-22(2026-07-01 更新:macOS killswitch 尝试后暂缓、回退 L0,详见 §6.5)
KillSwitch 的本质是 fail-closed:
目标是堵住 VPN 掉线瞬间的 IP / DNS 泄漏。没有 KillSwitch 时,隧道一掉,流量静默回落到运营商默认路由,用户真实 IP 与 DNS 查询直接暴露——对科学上网场景是致命泄漏。
| Level | 机制 | 能堵什么 | 堵不住什么 |
|---|---|---|---|
| L0 无 | 掉线即走明文 | — | 全泄漏 |
| L1 内核路由绑定(软) | 路由绑死隧道接口(如 sing-box strict_route) | 进程活着、隧道未建/重连时不泄漏 | 进程一死、接口被拆 → 路由恢复 → 泄漏 |
| L2 OS 防火墙强制(硬) | OS 级包过滤规则(NE 框架 / WFP / nftables)阻断非隧道流量 | 进程死了也照堵(规则在 OS 内核,不在 app) | 开机到规则生效前的窗口 |
| L3 常开 + 开机持久 | L2 + OS 从开机起自动拉起 VPN 并强制阻断 | 开机窗口也堵,全程 fail-closed | (基本无死角) |
理想态 = L3。
strict_route 都还停在 L1。
| 平台 | 机制 | 天花板 | 说明 |
|---|---|---|---|
| macOS | NetworkExtension System Extension | L3 | includeAllNetworks=true 全量入隧道 + NEOnDemandRule 常开 + enforceRoutes。NE 守护进程(neagent)在系统级强制,扛 app 崩溃。Apple 平台原生支持最完整。 |
| iOS | NetworkExtension | L3 | 同 NE 原语,甚至更干净;on-demand「按需常开」。⚠️ 目前无 iOS 客户端。 |
| Android | VpnService | L3,但有条件 | app 内只能到 L1(strict_route);真正的 L2/L3 = 系统设置「始终开启 VPN + 无 VPN 时阻止连接」,OS 级强制、扛崩溃、开机生效。但 app 不能编程开启,只能深链引导用户手动开(或 Device Owner/MDM 下发)。 |
| Windows | 子进程 sing-box + wintun | L2(需开发) | 现仅 strict_route(L1)。要到 L2 须让 app 装 WFP(Windows Filtering Platform)过滤器阻断非隧道流量;做成系统服务持久化才能扛崩溃。 |
| Linux(非主目标) | 子进程 + tun | L2(需开发) | L1 现成;L2 靠 nftables/iptables killswitch 链。 |
一句话:Apple 两端原生能直达 L3;Android 能到 L3 但要用户手动配合;Windows/Linux 要自己写 OS 防火墙规则才能上 L2。
| 平台 | 当前 Level | 真实状态 | 代码位置 |
|---|---|---|---|
| Windows | L1 | ✅ 改 strict_route + 子进程重载 + 退避重连。进程被硬杀仍泄漏。 | desktop_vpn_bridge.dart:305(applyKillSwitchToConfig)、:178 |
| Linux | L1 | 同 Windows(共用 DesktopVpnBridge)。 | 同上 |
| macOS | L0(暂缓) | ❌ stub:setKillSwitch 直接 result(nil)。曾尝试 enforceRoutes+on-demand,因性价比低 + 免费版配额重连 churn + includeAllNetworks 堵死整机网络,暂缓并回退 L0(#1 低优先级)。踩坑记录见 §6.5。 | VpnChannel.swift:89 |
| Android | L0 | ❌ stub:setKillSwitch 只打日志(TODO 11G)。 | MainActivity.kt(setKillSwitch 分支) |
| iOS | — | 无客户端。 | — |
注:macOS 自 kUseNativeVpnMacOS=true(vpn_bridge_provider.dart:19)起走原生 System Extension,不再走 DesktopVpnBridge。所以 macOS 的 strict_route(Windows 路线)对它不生效,必须走 NE 原生路线。
strict_route,与 Windows 一致),再引导用户开系统 Always-on 拿到 L3,UI 上诚实标注「彻底防泄漏需在系统设置开启」。这是「app 能力上限 + OS 兜底」的合理组合,不夸大。includeAllNetworks + on-demand)—— 天花板 L3、改动集中在 Swift,性价比最高。includeAllNetworks(唯一能给「扛进程被杀」L2 的原语)实测堵死整机网络见下;② 退化方案 enforceRoutes+on-demand 几乎等于没做(核心 fail-closed 没拿到);③ on-demand 常开对免费版 10 分钟配额造成重连 churn。综合判定性价比低,低优先级暂缓。下文保留踩坑记录与方案,便于将来重拾。macOS 走原生 NE System Extension(kUseNativeVpnMacOS=true),strict_route 那套对它不生效,必须用 NE 原语。
includeAllNetworks=true 追 L2,实测把整机网络堵死并陷入连不上的死循环——它会把所有流量(含 sing-box 连服务器的握手、app 调控制面 API 的请求)在隧道建起来之前就塞进隧道 → 握手出不去 → 连接失败 → on-demand 立刻重连 → 无限打转(连 app 的账户信息都拉不到,「我的」页全显示 —)。故撤掉 includeAllNetworks,改用下面不会误伤握手/控制面的组合。要拿回「扛进程被杀」那一档,须先在扩展内显式放行服务器/控制面连接,留待真机验证后再评估。| 当前达成 | NE 机制 | 效果 / 边界 |
|---|---|---|
| 连接期防泄漏 | enforceRoutes=true(隧道 includedRoutes 含 0.0.0.0/0) | 连接期间所有流量强制走隧道、不从旁路接口泄漏;不覆盖进程被杀后的窗口 |
| 掉线缺口收窄 | onDemandRules=[NEOnDemandRuleConnect()] + isOnDemandEnabled=true | 掉线/开机窗口 OS 自动拉起隧道,缩小重连缺口(非零) |
放行 LAN:excludeLocalNetworks=true(打印机/AirPlay 等)。不设 includeAllNetworks(见上方踩坑修正)。 | ||
即「连接期 enforceRoutes 防漏 + on-demand 兜重连」,介于 L1 与 L2 之间,尚未到设计追求的 includeAllNetworks-L3。诚实标注,不夸大。
client/macos/Runner/VpnChannel.swift
killSwitchEnabled(默认 true,与 Dart AppSettings.killSwitch 默认对齐)。setKillSwitch 落地:存标志;若 manager 已存在 → 重新 applyKillSwitchConfig + saveToPreferences 即时生效。configureKillSwitch 按标志设:enforceRoutes / excludeLocalNetworks=true / onDemandRules + isOnDemandEnabled;includeAllNetworks 恒 false。stop():on-demand 常开时直接 stopVPNTunnel() 会被 OS 立刻拉回。手动断开须先 isOnDemandEnabled=false + saveToPreferences 再 stop;否则「断开」按钮失效。client/macos/PacketTunnel/PacketTunnelProvider.swift:libbox 回调 includeAllNetworks() 保持 false(与 NE 层一致)。
无需新增 entitlement(packet-tunnel-provider 已有);enforceRoutes/excludeLocalNetworks 为 macOS 11+ API,已 #available 守卫,Runner 部署目标 10.15 满足。
flutter build macos 通过、sysext 激活。stop() 的 on-demand 处理)。scripts/local_test.sh ks-baseline|ks-test|ks-status 做 fail-closed 漏测。KillSwitch 相关待办见项目 todo/(/todo list),主要三条:
includeAllNetworks + on-demand)strict_route)+ 引导系统 Always-on(属 Android 客户端 11G)| 主题 | 文件 |
|---|---|
桥接契约(setKillSwitch 方法签名) | client/lib/bridge/vpn_bridge.dart |
| 平台分派(哪个平台走哪个 bridge) | client/lib/bridge/vpn_bridge_provider.dart |
| Windows/Linux 实现(strict_route) | client/lib/bridge/desktop_vpn_bridge.dart |
| macOS 原生通道(stub) | client/macos/Runner/VpnChannel.swift |
| macOS 隧道 Provider | client/macos/PacketTunnel/PacketTunnelProvider.swift |
| Android 通道(stub) | client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/MainActivity.kt |
| Android VPN 服务 | client/android/app/src/main/kotlin/com/pangolin/pangolin_vpn/PangolinVpnService.kt |