Files
pangolin/client/macos/PacketTunnel/PacketTunnelProvider.swift
T
wangjia 07272f5610
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
fix(client/macos): P1 骨架修正 — bundle id 统一 PacketTunnel + Info.plist 精简
- VpnChannel/Provider: 扩展 bundle id 统一为 com.pangolin.pangolin.PacketTunnel
  (对齐 Xcode 新建 target 的实际 id)
- PacketTunnel/Info.plist: 去掉重复 CFBundlePackageType + 多余 NEMachServiceName,
  只保留 NEProviderClasses(其余标准键由 GENERATE_INFOPLIST_FILE 合并)
- .gitignore: 忽略 Frameworks/Libbox.xcframework(204MB,build-libbox.sh 产出)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:50:51 +08:00

125 lines
6.1 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// PacketTunnelProvider.swift Pangolin macOS System Extension(B / P1 )
//
// : sysex NEPacketTunnelProvider sing-box(libbox)
// TUN, root( PoC `sudo sing-box run` )
//
// ( docs/p1-macos-system-extension.md):
// app(Flutter) NETunnelProviderManager.startVPNTunnel(options:) sing-box
// JSON startTunnel LibboxSetup(App Group ) LibboxNewService(json,
// platformInterface) start;libbox platformInterface.openTun ,
// NEPacketTunnelNetworkSettings + setTunnelNetworkSettings, packetFlow fd
// / libbox CommandServer(loopback) app CommandClient
//
// :
// 1. Libbox.xcframework(scripts/build-libbox.sh apple macos )
// 2. target NetworkExtension + Libbox
// 3. PangolinPlatformInterface LibboxPlatformInterfaceProtocol ****
// sing-box-for-apple ExtensionPlatformInterface ()
import NetworkExtension
import os
// import Libbox // gomobile bind ; xcframework
private let log = Logger(subsystem: "com.pangolin.pangolin.PacketTunnel", category: "provider")
class PacketTunnelProvider: NEPacketTunnelProvider {
// libbox ( Libbox , xcframework LibboxBoxService)
private var boxService: AnyObject?
private var platformInterface: PangolinPlatformInterface?
// App Group : sing-box (/) app
private static let appGroup = "group.com.pangolin.pangolin"
override func startTunnel(options: [String: NSObject]?) async throws {
log.info("startTunnel")
// 1) sing-box JSON: startVPNTunnel options["configContent"],
// 退 App Group ( app )
let configContent: String
if let inline = options?["configContent"] as? String, !inline.isEmpty {
configContent = inline
} else {
configContent = (try? Self.readSharedConfig()) ?? ""
}
guard !configContent.isEmpty else {
throw NSError(domain: "pangolin.tunnel", code: 1,
userInfo: [NSLocalizedDescriptionKey: "empty sing-box config"])
}
// 2) libbox ( App Group ,sysess )
guard let base = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: Self.appGroup) else {
throw NSError(domain: "pangolin.tunnel", code: 2,
userInfo: [NSLocalizedDescriptionKey: "no app group container"])
}
let work = base.appendingPathComponent("work", isDirectory: true)
try? FileManager.default.createDirectory(at: work, withIntermediateDirectories: true)
/* Libbox.xcframework 线
var setupError: NSError?
let setup = LibboxSetupOptions()
setup.basePath = base.path
setup.workingPath = work.path
setup.tempPath = NSTemporaryDirectory()
LibboxSetup(setup, &setupError)
if let setupError { throw setupError }
let platform = PangolinPlatformInterface(provider: self)
self.platformInterface = platform
var newError: NSError?
guard let service = LibboxNewService(configContent, platform, &newError) else {
throw newError ?? NSError(domain: "pangolin.tunnel", code: 3)
}
self.boxService = service
try service.start()
*/
// :xcframework ,startTunnel ,
throw NSError(domain: "pangolin.tunnel", code: 99, userInfo: [
NSLocalizedDescriptionKey:
"Libbox 尚未接入(见 docs/p1-macos-system-extension.md 步骤 3)",
])
}
override func stopTunnel(with reason: NEProviderStopReason) async {
log.info("stopTunnel reason=\(reason.rawValue)")
/* if let service = boxService as? LibboxBoxService { try? service.close() } */
boxService = nil
platformInterface = nil
}
// app sendProviderMessage (); libbox CommandServer
override func handleAppMessage(_ messageData: Data) async -> Data? {
nil
}
private static func readSharedConfig() throws -> String {
guard let base = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
throw NSError(domain: "pangolin.tunnel", code: 2)
}
let url = base.appendingPathComponent("config.json")
return try String(contentsOf: url, encoding: .utf8)
}
}
/// libbox NetworkExtension (LibboxPlatformInterfaceProtocol)
///
/// openTun:libbox (//DNS/MTU),
/// NEPacketTunnelNetworkSettingssetTunnelNetworkSettings, packetFlow tun fd
///
/// (openTun / writeLog / useProcFS / findConnectionOwner /
/// defaultInterfaceMonitor / getInterfaces / underNetworkExtension / systemCertificates )
/// ExtensionPlatformInterface 3
final class PangolinPlatformInterface: NSObject {
private weak var provider: NEPacketTunnelProvider?
init(provider: NEPacketTunnelProvider) {
self.provider = provider
}
// :openTun libbox NEPacketTunnelNetworkSettings
// Libbox (LibboxTunOptions / tun fd)
}