feat(macos): KillSwitch L0→L3 — NE includeAllNetworks+enforceRoutes+on-demand

macOS 走原生 NE,strict_route 不生效。补齐断网保护到天花板 L3:

VpnChannel.swift
- 缓存 killSwitchEnabled(默认 true,与 Dart AppSettings.killSwitch 对齐)
- setKillSwitch 落地:存标志,manager 已装配则即时重写 NE 配置+保存
- configureKillSwitch:includeAllNetworks+enforceRoutes(L2 OS 强制、扛崩溃)
  + NEOnDemandRule 常开(L3)+ excludeLocalNetworks 放行 LAN
  (enforceRoutes/excludeLocalNetworks 为 macOS 11+ API,#available 守卫)
- stop() gotcha:on-demand 常开时先关 isOnDemandEnabled+save 再 stop,
  否则手动断开被 OS 立刻拉回
- start() options 带 killSwitch 传扩展

PacketTunnelProvider.swift
- startTunnel 读 killSwitch 选项(OS on-demand 自启时 nil→默认 true)
- libbox 回调 includeAllNetworks() 返回该值,与 NE 层对齐

docs/killswitch-design.html: 新增 §6.5 实现方案,状态表 macOS L0→L3

验证:xcodebuild CODE_SIGNING_ALLOWED=NO 两 target SwiftCompile 通过;
端到端 fail-closed/on-demand 需真机手测(NE 难单测)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 23:54:32 +08:00
parent e2440e44cc
commit 680f1af205
3 changed files with 122 additions and 8 deletions
@@ -63,7 +63,12 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
LibboxSetup(setup, &setupErr)
if let setupErr { throw setupErr }
let platform = PangolinPlatformInterface(provider: self)
// KillSwitch: app startVPNTunnel options ;OS on-demand
// options nil true(on-demand killswitch )libbox
// includeAllNetworks() NE includeAllNetworks ,
let killSwitch = (options?["killSwitch"] as? NSNumber)?.boolValue ?? true
log.info("startTunnel: killSwitch=\(killSwitch, privacy: .public)")
let platform = PangolinPlatformInterface(provider: self, includeAllNetworks: killSwitch)
self.platform = platform
var newErr: NSError?
@@ -182,9 +187,11 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol
private var monitor: NWPathMonitor?
private var defaultInterfaceIndex: Int32 = -1
private let monitorQueue = DispatchQueue(label: "pangolin.tunnel.pathmonitor")
private let includeAllNetworksFlag: Bool
init(provider: NEPacketTunnelProvider) {
init(provider: NEPacketTunnelProvider, includeAllNetworks: Bool) {
self.provider = provider
self.includeAllNetworksFlag = includeAllNetworks
super.init()
}
@@ -269,7 +276,7 @@ final class PangolinPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol
// MARK: ()
func underNetworkExtension() -> Bool { true }
func useProcFS() -> Bool { false }
func includeAllNetworks() -> Bool { false }
func includeAllNetworks() -> Bool { includeAllNetworksFlag }
func clearDNSCache() {}
func readWIFIState() -> LibboxWIFIState? { nil }
func systemCertificates() -> (any LibboxStringIteratorProtocol)? { nil }