From fb1ef9ba01b7e1b0791290dd7531182da5022ecb Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 19 Jun 2026 15:54:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(client/macos):=20OSSystemExtensionRequest?= =?UTF-8?q?=20=E6=BF=80=E6=B4=BB=20sysext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VpnChannel.activateSystemExtensionIfNeeded 实装:start 前请求系统加载/更新 PacketTunnel sysext;首启弹「隐私与安全性」待用户允许(await 阻塞到批准, 扩展没加载隧道起不来)。SysExtActivationDelegate 处理 finish/fail/replace。 至此 macOS 原生 VPN 栈构建完整:Runner 嵌入 sysext 到 Contents/Library/SystemExtensions,编译全过。运行待:sysext 签名放行 + systemextensionsctl developer on(或 Developer ID 公证)。 Co-Authored-By: Claude Opus 4.8 --- client/macos/Runner/VpnChannel.swift | 45 +++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/client/macos/Runner/VpnChannel.swift b/client/macos/Runner/VpnChannel.swift index 4057510..55f8b29 100644 --- a/client/macos/Runner/VpnChannel.swift +++ b/client/macos/Runner/VpnChannel.swift @@ -12,6 +12,7 @@ import FlutterMacOS import NetworkExtension +import SystemExtensions // NSLog 兼容老部署目标(Runner < macOS 11,os.Logger 不可用)。 private func vpnLog(_ message: String) { NSLog("[pangolin/vpn] %@", message) } @@ -24,6 +25,7 @@ final class VpnChannel: NSObject { private var statusObserver: NSObjectProtocol? private var statsTimer: Timer? private var manager: NETunnelProviderManager? + private var sysextDelegate: SysExtActivationDelegate? static func register(with registrar: FlutterPluginRegistrar) { let instance = VpnChannel() @@ -100,11 +102,17 @@ final class VpnChannel: NSObject { return mgr } - // 站外 sysex 首次需用户授权激活。 + // 请求系统加载/更新 PacketTunnel System Extension。首启系统会弹「隐私与安全性」 + // 让用户允许;允许后 didFinishWithResult 回来。已是最新则快速完成。 private func activateSystemExtensionIfNeeded() async throws { - // TODO(P1): OSSystemExtensionRequest.activationRequest( - // forExtensionWithIdentifier: tunnelBundleId, queue: .main) + delegate 等待结果。 - // 已激活则直接返回。详见安装文档步骤 4。 + try await withCheckedThrowingContinuation { (cont: CheckedContinuation) in + let req = OSSystemExtensionRequest.activationRequest( + forExtensionWithIdentifier: Self.tunnelBundleId, queue: .main) + let delegate = SysExtActivationDelegate(continuation: cont) + self.sysextDelegate = delegate // 保活到回调结束 + req.delegate = delegate + OSSystemExtensionManager.shared.submitRequest(req) + } } // ── 状态 / 速率回传 ───────────────────────────────────────────── @@ -161,3 +169,32 @@ private final class StatsStreamHandler: NSObject, FlutterStreamHandler { } func onCancel(withArguments _: Any?) -> FlutterError? { owner?.onStatsCancel(); return nil } } + +// System Extension 激活请求回调。首启需用户在「系统设置→隐私与安全性」允许, +// 允许后收到 didFinishWithResult(故 await 会阻塞到用户批准 —— 这是对的, +// 扩展没加载前隧道起不来)。 +private final class SysExtActivationDelegate: NSObject, OSSystemExtensionRequestDelegate { + private let continuation: CheckedContinuation + private var resumed = false + init(continuation: CheckedContinuation) { self.continuation = continuation } + + func request(_ request: OSSystemExtensionRequest, + didFinishWithResult result: OSSystemExtensionRequest.Result) { + guard !resumed else { return } + resumed = true + continuation.resume() + } + func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { + guard !resumed else { return } + resumed = true + continuation.resume(throwing: error) + } + func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { + vpnLog("system extension 待用户允许(系统设置 → 隐私与安全性)") + } + func request(_ request: OSSystemExtensionRequest, + actionForReplacingExtension existing: OSSystemExtensionProperties, + withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { + .replace + } +}