@@ -0,0 +1,164 @@
// V p n C h a n n e l . s w i f t — 主 a p p 侧 V P N 控 制 ( 方 案 B / P 1 骨 架 )
//
// 把 F l u t t e r 的 p a n g o l i n / v p n ( M e t h o d C h a n n e l ) + p a n g o l i n / v p n / s t a t u s 、 p a n g o l i n / v p n / s t a t s
// ( E v e n t C h a n n e l ) 接 到 N E T u n n e l P r o v i d e r M a n a g e r : 安 装 / 启 停 P a c k e t T u n n e l S y s t e m E x t e n s i o n 、
// 回 传 状 态 与 速 率 。 契 约 对 齐 c l i e n t / l i b / b r i d g e / v p n _ b r i d g e . d a r t 的 V p n N a t i v e B r i d g e 。
//
// 注 册 : 在 M a i n F l u t t e r W i n d o w . a w a k e F r o m N i b 里 ` V p n C h a n n e l . r e g i s t e r ( w i t h : f l u t t e r V i e w C o n t r o l l e r ) ` 。
//
// ⚠ ️ 站 外 分 发 首 启 需 先 * * 激 活 S y s t e m E x t e n s i o n * * ( O S S y s t e m E x t e n s i o n R e q u e s t , 见
// a c t i v a t e S y s t e m E x t e n s i o n I f N e e d e d + 安 装 文 档 步 骤 4 ) , 用 户 会 在 「 系 统 设 置 → 隐 私 与 安 全 性 」
// 点 允 许 ; 激 活 成 功 后 N E T u n n e l P r o v i d e r M a n a g e r 才 能 加 载 该 p r o v i d e r 。
import FlutterMacOS
import NetworkExtension
import SystemExtensions
import os
private let log = Logger ( subsystem : " com.pangolin.pangolin " , category : " vpn " )
final class VpnChannel : NSObject {
private static let tunnelBundleId = " com.pangolin.pangolin.tunnel "
private var statusSink : FlutterEventSink ?
private var statsSink : FlutterEventSink ?
private var statusObserver : NSObjectProtocol ?
private var statsTimer : Timer ?
private var manager : NETunnelProviderManager ?
static func register ( with registrar : FlutterPluginRegistrar ) {
let instance = VpnChannel ( )
let method = FlutterMethodChannel ( name : " pangolin/vpn " ,
binaryMessenger : registrar . messenger )
method . setMethodCallHandler ( instance . handle )
FlutterEventChannel ( name : " pangolin/vpn/status " , binaryMessenger : registrar . messenger )
. setStreamHandler ( StatusStreamHandler ( owner : instance ) )
FlutterEventChannel ( name : " pangolin/vpn/stats " , binaryMessenger : registrar . messenger )
. setStreamHandler ( StatsStreamHandler ( owner : instance ) )
instance . observeStatus ( )
}
// ─ ─ M e t h o d C h a n n e l ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
private func handle ( _ call : FlutterMethodCall , _ result : @ escaping FlutterResult ) {
switch call . method {
case " start " :
guard let configJson = call . arguments as ? String else {
result ( FlutterError ( code : " bad_args " , message : " expected config json " , details : nil ) )
return
}
Task { await self . start ( configJson , result ) }
case " stop " :
Task { await self . stop ( result ) }
case " getStatus " :
result ( Self . statusString ( manager ? . connection . status ? ? . invalid ) )
case " selectOutbound " :
// TODO: 经 s e n d P r o v i d e r M e s s a g e / l i b b o x C o m m a n d C l i e n t 切 o u t b o u n d 。
result ( nil )
case " getActiveOutbound " :
result ( " auto " )
case " setKillSwitch " :
// i n c l u d e A l l N e t w o r k s / o n - d e m a n d 实 现 k i l l s w i t c h ( 后 续 ) 。
result ( nil )
default :
result ( FlutterMethodNotImplemented )
}
}
private func start ( _ configJson : String , _ result : @ escaping FlutterResult ) async {
do {
try await activateSystemExtensionIfNeeded ( )
let mgr = try await loadOrCreateManager ( )
self . manager = mgr
try mgr . connection . startVPNTunnel ( options : [
" configContent " : configJson as NSString ,
] )
result ( nil )
} catch {
log . error ( " start failed: \( error . localizedDescription ) " )
result ( FlutterError ( code : " start_failed " , message : error . localizedDescription , details : nil ) )
}
}
private func stop ( _ result : @ escaping FlutterResult ) async {
manager ? . connection . stopVPNTunnel ( )
result ( nil )
}
// ─ ─ N E T u n n e l P r o v i d e r M a n a g e r 装 配 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
private func loadOrCreateManager ( ) async throws -> NETunnelProviderManager {
let all = try await NETunnelProviderManager . loadAllFromPreferences ( )
let mgr = all . first ? ? NETunnelProviderManager ( )
let proto = ( mgr . protocolConfiguration as ? NETunnelProviderProtocol ) ? ? NETunnelProviderProtocol ( )
proto . providerBundleIdentifier = Self . tunnelBundleId
proto . serverAddress = " Pangolin " // 仅 展 示 用
mgr . protocolConfiguration = proto
mgr . localizedDescription = " Pangolin "
mgr . isEnabled = true
try await mgr . saveToPreferences ( )
try await mgr . loadFromPreferences ( ) // 保 存 后 重 载 , 拿 到 有 效 c o n n e c t i o n
return mgr
}
// 站 外 s y s e x 首 次 需 用 户 授 权 激 活 。
private func activateSystemExtensionIfNeeded ( ) async throws {
// T O D O ( P 1 ) : O S S y s t e m E x t e n s i o n R e q u e s t . a c t i v a t i o n R e q u e s t (
// f o r E x t e n s i o n W i t h I d e n t i f i e r : t u n n e l B u n d l e I d , q u e u e : . m a i n ) + d e l e g a t e 等 待 结 果 。
// 已 激 活 则 直 接 返 回 。 详 见 安 装 文 档 步 骤 4 。
}
// ─ ─ 状 态 / 速 率 回 传 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
private func observeStatus ( ) {
statusObserver = NotificationCenter . default . addObserver (
forName : . NEVPNStatusDidChange , object : nil , queue : . main
) { [ weak self ] note in
guard let conn = note . object as ? NEVPNConnection else { return }
self ? . statusSink ? ( Self . statusString ( conn . status ) )
}
}
fileprivate func onStatusListen ( _ sink : @ escaping FlutterEventSink ) {
statusSink = sink
sink ( Self . statusString ( manager ? . connection . status ? ? . invalid ) )
}
fileprivate func onStatusCancel ( ) { statusSink = nil }
fileprivate func onStatsListen ( _ sink : @ escaping FlutterEventSink ) {
statsSink = sink
// T O D O ( P 1 ) : 接 l i b b o x C o m m a n d C l i e n t 订 阅 实 时 速 率 ; 骨 架 先 每 秒 推 占 位 0 。
statsTimer = Timer . scheduledTimer ( withTimeInterval : 1 , repeats : true ) { [ weak self ] _ in
self ? . statsSink ? ( [ " up " : 0 , " down " : 0 , " uplinkTotal " : 0 , " downlinkTotal " : 0 ] )
}
}
fileprivate func onStatsCancel ( ) { statsTimer ? . invalidate ( ) ; statsTimer = nil ; statsSink = nil }
private static func statusString ( _ s : NEVPNStatus ) -> String {
switch s {
case . connected : return " on "
case . connecting , . reasserting : return " connecting "
case . disconnecting : return " disconnecting "
case . disconnected , . invalid : return " off "
@ unknown default : return " error "
}
}
}
// E v e n t C h a n n e l 流 处 理 器 。
private final class StatusStreamHandler : NSObject , FlutterStreamHandler {
weak var owner : VpnChannel ?
init ( owner : VpnChannel ) { self . owner = owner }
func onListen ( withArguments _ : Any ? , eventSink events : @ escaping FlutterEventSink ) -> FlutterError ? {
owner ? . onStatusListen ( events ) ; return nil
}
func onCancel ( withArguments _ : Any ? ) -> FlutterError ? { owner ? . onStatusCancel ( ) ; return nil }
}
private final class StatsStreamHandler : NSObject , FlutterStreamHandler {
weak var owner : VpnChannel ?
init ( owner : VpnChannel ) { self . owner = owner }
func onListen ( withArguments _ : Any ? , eventSink events : @ escaping FlutterEventSink ) -> FlutterError ? {
owner ? . onStatsListen ( events ) ; return nil
}
func onCancel ( withArguments _ : Any ? ) -> FlutterError ? { owner ? . onStatsCancel ( ) ; return nil }
}