From 6bcdbbe752766abc1f5b524cbcd4dee5001840f7 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 1 Jul 2026 06:38:47 +0800 Subject: [PATCH] =?UTF-8?q?revert(macos):=20killswitch=20=E5=9B=9E?= =?UTF-8?q?=E9=80=80=20L0(=E4=BD=8E=E4=BC=98=E5=85=88=E7=BA=A7=E6=9A=82?= =?UTF-8?q?=E7=BC=93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户判定 killswitch 优先级低、暂不做: - includeAllNetworks 实测堵死整机网络;退化方案 enforceRoutes+on-demand 几乎等于没做(核心 fail-closed 没拿到),on-demand 还对免费版配额造成 重连 churn —— 性价比低 - VpnChannel.swift 恢复到 L0 stub(setKillSwitch→result(nil)); PacketTunnelProvider.swift 已是原样 - docs §6.5 改为「尝试与暂缓」记录,状态表 macOS 回 L0 - local_test.sh 的 ks-* 漏测子命令保留(将来重拾 killswitch 时可用) Co-Authored-By: Claude Opus 4.8 --- client/macos/Runner/VpnChannel.swift | 78 +--------------------------- docs/killswitch-design.html | 11 ++-- 2 files changed, 8 insertions(+), 81 deletions(-) diff --git a/client/macos/Runner/VpnChannel.swift b/client/macos/Runner/VpnChannel.swift index c3fe304..e2105b9 100644 --- a/client/macos/Runner/VpnChannel.swift +++ b/client/macos/Runner/VpnChannel.swift @@ -33,10 +33,6 @@ final class VpnChannel: NSObject { private let statsClient = StatsClient() private var manager: NETunnelProviderManager? private var sysextDelegate: SysExtActivationDelegate? - // KillSwitch(断网保护)开关,默认 true 与 Dart AppSettings.killSwitch 默认对齐。 - // 决定 NE 层是否 fail-closed:includeAllNetworks + enforceRoutes(L2 OS 强制) - // + NEOnDemandRule 常开(L3 开机/掉线窗口也堵)。详见 docs/killswitch-design.html §6.5。 - private var killSwitchEnabled = true static func register(with registrar: FlutterPluginRegistrar) { let instance = VpnChannel() @@ -91,26 +87,8 @@ final class VpnChannel: NSObject { case "getActiveOutbound": result("auto") case "setKillSwitch": - let on = (call.arguments as? Bool) ?? true - killSwitchEnabled = on - vpnLog("setKillSwitch=\(on)") - // 已装配 manager 则即时重写 NE 配置(includeAllNetworks/enforceRoutes/on-demand); - // 尚无 manager 时仅缓存,下次 loadOrCreateManager 装配时应用。 - if let mgr = manager { - Task { - do { - try await self.applyKillSwitchConfig(to: mgr) - vpnLog("setKillSwitch: NE 配置已更新并保存 ✓") - result(nil) - } catch { - let ns = error as NSError - vpnLog("setKillSwitch: 保存 NE 配置失败 ✗ code=\(ns.code) desc=\(ns.localizedDescription)") - result(FlutterError(code: "killswitch_failed", message: error.localizedDescription, details: nil)) - } - } - } else { - result(nil) - } + // includeAllNetworks / on-demand 实现 kill switch(后续)。 + result(nil) default: result(FlutterMethodNotImplemented) } @@ -142,19 +120,6 @@ final class VpnChannel: NSObject { } private func stop(_ result: @escaping FlutterResult) async { - // on-demand 常开时直接 stopVPNTunnel 会被 OS 立刻拉回 → 手动断开须先关 on-demand 再断。 - // 下次手动 start() 时 loadOrCreateManager 会按 killSwitchEnabled 重新启用 on-demand。 - if let mgr = manager, mgr.isOnDemandEnabled { - mgr.isOnDemandEnabled = false - do { - try await mgr.saveToPreferences() - try await mgr.loadFromPreferences() - vpnLog("stop: 已临时关闭 on-demand(避免手动断开被自动拉回)") - } catch { - let ns = error as NSError - vpnLog("stop: 关闭 on-demand 失败(仍继续断开) code=\(ns.code) desc=\(ns.localizedDescription)") - } - } manager?.connection.stopVPNTunnel() result(nil) } @@ -170,7 +135,6 @@ final class VpnChannel: NSObject { mgr.protocolConfiguration = proto mgr.localizedDescription = "Pangolin" mgr.isEnabled = true - configureKillSwitch(on: mgr) // 按 killSwitchEnabled 写入 NE fail-closed 字段 vpnLog(" saveToPreferences(providerBundleId=\(Self.tunnelBundleId)) …") try await mgr.saveToPreferences() try await mgr.loadFromPreferences() // 保存后重载,拿到有效 connection @@ -178,44 +142,6 @@ final class VpnChannel: NSObject { return mgr } - // ── KillSwitch(断网保护)NE 配置 ──────────────────────────────── - // 把当前 killSwitchEnabled 写入 manager 的 NE 字段(不保存,由调用方保存): - // enforceRoutes=true:隧道 includedRoutes(0.0.0.0/0 默认路由)优先于物理接口, - // 连接期间所有流量强制走隧道、不从旁路接口泄漏。 - // NEOnDemandRule 常开:OS 在掉线/开机窗口自动拉起隧道,缩小重连缺口。 - // excludeLocalNetworks=true:放行 LAN(打印机/AirPlay 等),不影响防泄漏目标。 - // ⚠️ 刻意不设 includeAllNetworks:它会把「所有」流量(含 sing-box 连服务器的握手、 - // app 调控制面的请求)在隧道建起来前就塞进隧道 → 握手出不去 → 连接失败 + on-demand - // 死循环 + 整机断网(实测)。要拿「扛进程被杀」那一档须先在扩展内正确放行服务器/ - // 控制面连接,留待真机验证后再评估,见 docs/killswitch-design.html §6.5。 - // enforceRoutes/excludeLocalNetworks 为 macOS 11+ API,Runner 部署目标 10.15 → #available 守卫。 - private func configureKillSwitch(on mgr: NETunnelProviderManager) { - if let proto = mgr.protocolConfiguration as? NETunnelProviderProtocol { - proto.includeAllNetworks = false - if #available(macOS 11.0, *) { - proto.enforceRoutes = killSwitchEnabled - proto.excludeLocalNetworks = true - } - } - if killSwitchEnabled { - let rule = NEOnDemandRuleConnect() - rule.interfaceTypeMatch = .any - mgr.onDemandRules = [rule] - mgr.isOnDemandEnabled = true - } else { - mgr.onDemandRules = [] - mgr.isOnDemandEnabled = false - } - vpnLog(" killSwitch 配置: enforceRoutes=\(killSwitchEnabled) onDemand=\(killSwitchEnabled) includeAllNetworks=false") - } - - /// 即时应用 killswitch(改字段 + 保存 + 重载)。供 setKillSwitch 在 manager 已装配时调用。 - private func applyKillSwitchConfig(to mgr: NETunnelProviderManager) async throws { - configureKillSwitch(on: mgr) - try await mgr.saveToPreferences() - try await mgr.loadFromPreferences() - } - // 请求系统加载/更新 PacketTunnel System Extension。首启系统会弹「隐私与安全性」 // 让用户允许;允许后 didFinishWithResult 回来。已是最新则快速完成。 private func activateSystemExtensionIfNeeded() async throws { diff --git a/docs/killswitch-design.html b/docs/killswitch-design.html index 35a0cb1..53eb8d6 100644 --- a/docs/killswitch-design.html +++ b/docs/killswitch-design.html @@ -50,7 +50,7 @@ ← 文档索引

KillSwitch 设计与跨平台能力矩阵(知识库)

-

Pangolin 客户端 · 断网保护设计依据与现状 · 2026-06-22(2026-07-01 更新:macOS killswitch 落地 enforceRoutes+on-demand;includeAllNetworks 实测堵死整机网络已撤回)

+

Pangolin 客户端 · 断网保护设计依据与现状 · 2026-06-22(2026-07-01 更新:macOS killswitch 尝试后暂缓、回退 L0,详见 §6.5)

KillSwitch 不是「一个开关」,而是分层能力——理想态、平台天花板、当前实现是三件不同的事。本文沉淀其设计依据与现状。 @@ -104,7 +104,7 @@ KillSwitch 不是「一个开关」,而是分层能力——理想态 WindowsL1✅ 改 strict_route + 子进程重载 + 退避重连。进程被硬杀仍泄漏。desktop_vpn_bridge.dart:305applyKillSwitchToConfig)、:178 LinuxL1同 Windows(共用 DesktopVpnBridge)。同上 -macOSL1→L2 之间✅ 本轮 #1:enforceRoutes 连接期防漏 + NEOnDemandRule 常开兜重连;随 killswitch 开关即时切换,手动断开先关 on-demand 防反弹。⚠️ includeAllNetworks 实测堵死整机网络已撤回,故暂未到 includeAllNetworks-L3。详见 §6.5。VpnChannel.swiftconfigureKillSwitch) +macOSL0(暂缓)❌ stub:setKillSwitch 直接 result(nil)。曾尝试 enforceRoutes+on-demand,因性价比低 + 免费版配额重连 churn + includeAllNetworks 堵死整机网络,暂缓并回退 L0(#1 低优先级)。踩坑记录见 §6.5。VpnChannel.swift:89 AndroidL0❌ stub:setKillSwitch 只打日志(TODO 11G)。MainActivity.ktsetKillSwitch 分支) iOS—无客户端。— @@ -113,7 +113,7 @@ KillSwitch 不是「一个开关」,而是分层能力——理想态

6. Pangolin 现实判断与决策

-

6.5 macOS KillSwitch 实现方案(本轮 #1)

+

6.5 macOS KillSwitch 尝试与暂缓(#1,低优先级搁置)

+
结论(2026-07-01):尝试落地后已回退到 L0(stub)。原因:① 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,改用下面不会误伤握手/控制面的组合。要拿回「扛进程被杀」那一档,须先在扩展内显式放行服务器/控制面连接,留待真机验证后再评估。
@@ -137,7 +138,7 @@ KillSwitch 不是「一个开关」,而是分层能力——理想态

即「连接期 enforceRoutes 防漏 + on-demand 兜重连」,介于 L1 与 L2 之间,尚未到设计追求的 includeAllNetworks-L3。诚实标注,不夸大。

-

改动(2 个原生文件,不动 Dart/服务端)

+

曾经的改动方案(已回退,留作将来参考)

client/macos/Runner/VpnChannel.swift