feat(client/macos): OSSystemExtensionRequest 激活 sysext
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 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
import SystemExtensions
|
||||||
|
|
||||||
// NSLog 兼容老部署目标(Runner < macOS 11,os.Logger 不可用)。
|
// NSLog 兼容老部署目标(Runner < macOS 11,os.Logger 不可用)。
|
||||||
private func vpnLog(_ message: String) { NSLog("[pangolin/vpn] %@", message) }
|
private func vpnLog(_ message: String) { NSLog("[pangolin/vpn] %@", message) }
|
||||||
@@ -24,6 +25,7 @@ final class VpnChannel: NSObject {
|
|||||||
private var statusObserver: NSObjectProtocol?
|
private var statusObserver: NSObjectProtocol?
|
||||||
private var statsTimer: Timer?
|
private var statsTimer: Timer?
|
||||||
private var manager: NETunnelProviderManager?
|
private var manager: NETunnelProviderManager?
|
||||||
|
private var sysextDelegate: SysExtActivationDelegate?
|
||||||
|
|
||||||
static func register(with registrar: FlutterPluginRegistrar) {
|
static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
let instance = VpnChannel()
|
let instance = VpnChannel()
|
||||||
@@ -100,11 +102,17 @@ final class VpnChannel: NSObject {
|
|||||||
return mgr
|
return mgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// 站外 sysex 首次需用户授权激活。
|
// 请求系统加载/更新 PacketTunnel System Extension。首启系统会弹「隐私与安全性」
|
||||||
|
// 让用户允许;允许后 didFinishWithResult 回来。已是最新则快速完成。
|
||||||
private func activateSystemExtensionIfNeeded() async throws {
|
private func activateSystemExtensionIfNeeded() async throws {
|
||||||
// TODO(P1): OSSystemExtensionRequest.activationRequest(
|
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
|
||||||
// forExtensionWithIdentifier: tunnelBundleId, queue: .main) + delegate 等待结果。
|
let req = OSSystemExtensionRequest.activationRequest(
|
||||||
// 已激活则直接返回。详见安装文档步骤 4。
|
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 }
|
func onCancel(withArguments _: Any?) -> FlutterError? { owner?.onStatsCancel(); return nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// System Extension 激活请求回调。首启需用户在「系统设置→隐私与安全性」允许,
|
||||||
|
// 允许后收到 didFinishWithResult(故 await 会阻塞到用户批准 —— 这是对的,
|
||||||
|
// 扩展没加载前隧道起不来)。
|
||||||
|
private final class SysExtActivationDelegate: NSObject, OSSystemExtensionRequestDelegate {
|
||||||
|
private let continuation: CheckedContinuation<Void, Error>
|
||||||
|
private var resumed = false
|
||||||
|
init(continuation: CheckedContinuation<Void, Error>) { 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user