e129f094c2
### 新增文件 - client/ios/PacketTunnel/MemoryMonitor.swift 周期打点 NE 进程内存(os_proc_available_memory iOS13+ / mach_task_basic_info 兼容 iOS12);每 10s 采样一次;availableMemory < 5MB 时输出 ⚠️ 警告;提供 summaryString() 供 handleAppMessage/getMemory IPC 返回。 - client/ios/PacketTunnel/PacketTunnel.entitlements Extension 的 NE + App Group entitlement 文件;申请说明写入注释。 - client/ios/Runner/Runner.entitlements 主 App 的 NE + App Group entitlement 文件;申请说明写入注释。 - doc/ne-entitlement-申请指引.md 完整申请操作手册:Bundle ID 清单、Apple Developer Portal 步骤、 专项审批英文申请文案(已脱敏,无红线词)、审批 lead time 说明。 ### 修改文件 - client/ios/PacketTunnel/PacketTunnelProvider.swift 完整 M3 实现(替换原骨架 TODO): · startTunnel:解析 configJson(options/App Group 两路回退)→ buildNetworkSettings → setTunnelNetworkSettings → setupTunBridge → startLibbox → startPacketBridging → memoryMonitor.start() · stopTunnel:memoryMonitor.stop() → stopLibbox → closeTunBridge · handleAppMessage:JSON IPC 协议(getStatus/getMemory/selectOutbound) · LibboxPlatformInterface 扩展(#if canImport(Libbox)): openTun 返回 socketpair libbox 侧 fd,autoDetectInterfaceControl no-op, writeLog 转 os.log,useProcFS 返回 false · packetFlow 双向桥接(readFromPacketFlow/writeToPacketFlow 循环) · parseTunAddress/parseDNSServers 从 JSON 提取网络参数 - client/ios/Runner/VpnManager.swift · localizedDescription "穿山甲 Pangolin VPN" → "Pangolin 加速"(红线词修复) · 新增 appGroup 常量(group.com.pangolin.pangolinVpn) · start() 写入 App Group UserDefaults 缓存 configJson - client/ios/Runner/Info.plist NSVPNUsageDescription "穿山甲使用 VPN…" → "Pangolin 使用网络加速通道…"(红线词修复) - client/ios/PacketTunnel/Info.plist CFBundleDisplayName "穿山甲 Tunnel" → "Pangolin 加速通道"(红线词修复) - client/ios/Runner.xcodeproj/project.pbxproj · Runner + PacketTunnel 所有 build config 添加 CODE_SIGN_ENTITLEMENTS · 新增 MemoryMonitor.swift 到 PacketTunnel Sources 构建阶段 · 新增 Runner.entitlements / PacketTunnel.entitlements / MemoryMonitor.swift 文件引用及分组 - app/kernel/build-ios.sh 添加 M3 内存裁剪决策记录:gVisor→grpc→QUIC 优先裁减顺序及内存估算, 等待真机 10min 压测结果后更新最终结论 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
522 lines
20 KiB
Swift
522 lines
20 KiB
Swift
// PacketTunnelProvider.swift — Packet Tunnel Extension 完整实现(M3 PoC)
|
||
//
|
||
// Target: PacketTunnel (NEPacketTunnelProvider extension)
|
||
// Bundle: com.pangolin.pangolinVpn.PacketTunnel
|
||
// Entitlement: com.apple.developer.networking.networkextension / packet-tunnel-provider
|
||
//
|
||
// 架构概述:
|
||
// startTunnel → 解析 configJson → NEPacketTunnelNetworkSettings
|
||
// → LibboxNewService(libbox/sing-box 内核)→ 启动内核
|
||
// → 包流桥接(packetFlow ↔ socketpair ↔ libbox TUN fd)
|
||
// → MemoryMonitor 开始打点
|
||
// stopTunnel → 内核干净停止 → MemoryMonitor 输出摘要
|
||
// handleAppMessage → JSON IPC:getStatus / getMemory / selectOutbound
|
||
//
|
||
// libbox 依赖:
|
||
// 编译时需将 app/kernel/dist/ios/Libbox.xcframework 嵌入工程。
|
||
// gomobile bind ./experimental/libbox 生成 ObjC 桥:
|
||
// - LibboxNewService(configContent:platformInterface:disableMemoryLimit:error:)
|
||
// - LibboxService.start() / .stop()
|
||
// - LibboxPlatformInterface (ObjC protocol)
|
||
// - LibboxTunOptions (ObjC class, fields: inet4Address, inet6Address, mtu, autoRoute, …)
|
||
//
|
||
// 条件编译:
|
||
// #if canImport(Libbox) → 真机/设备 Release;否则走骨架路径,仅记录日志。
|
||
//
|
||
// 内存上限(最大风险):
|
||
// 旧设备 NE 进程 ~15MB;新设备 ~50MB。
|
||
// MemoryMonitor 每 10s 打点;若可用内存 < 5MB 输出 ⚠️ 警告。
|
||
// 若在真机测试中触顶(jetsam 杀进程),回 app/kernel/build-ios.sh 裁减
|
||
// build tags(优先去 with_gvisor → with_grpc),重出 XCFramework。
|
||
//
|
||
// IPC 协议(handleAppMessage / sendProviderMessage):
|
||
// 请求 JSON:{ "action": "getStatus" | "getMemory" | "selectOutbound", "tag"?: "xxx" }
|
||
// 响应 JSON:{ "status": "...", "memory": "...", "error": "..." }
|
||
//
|
||
// 红线词检查:
|
||
// 本文件无用户可见文案,所有 localizedDescription 见 VpnManager.swift。
|
||
|
||
import NetworkExtension
|
||
import Darwin
|
||
import Foundation
|
||
import os.log
|
||
|
||
#if canImport(Libbox)
|
||
import Libbox
|
||
#endif
|
||
|
||
// MARK: – PacketTunnelProvider
|
||
|
||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||
|
||
// ── 常量 ────────────────────────────────────────────────────
|
||
private let log = OSLog(
|
||
subsystem: "com.pangolin.pangolinVpn.PacketTunnel",
|
||
category: "PacketTunnel"
|
||
)
|
||
private let appGroup = "group.com.pangolin.pangolinVpn"
|
||
/// NE 进程默认隧道 IPv4 地址(回退值,正常由 config 中 tun.address 覆盖)
|
||
private let defaultTunAddr = "172.19.0.1"
|
||
private let defaultTunMask = "255.255.255.0"
|
||
/// 隧道内部 DNS(由 sing-box DNS 接管)
|
||
private let defaultDNS = "198.18.0.1"
|
||
/// MTU(NE 建议值 1500,sing-box tun 默认 9000 - 受 NE 限制降低)
|
||
private let tunMTU: NSNumber = 1500
|
||
|
||
// ── 运行时状态 ──────────────────────────────────────────────
|
||
private let memoryMonitor = MemoryMonitor()
|
||
|
||
#if canImport(Libbox)
|
||
private var libboxService: LibboxService?
|
||
#endif
|
||
|
||
/// 桥接用 socketpair([0]=NE侧,[1]=libbox侧)
|
||
private var tunFDs: (ne: Int32, libbox: Int32) = (-1, -1)
|
||
private var isStopped = false
|
||
private let stopLock = NSLock()
|
||
|
||
/// 上次 selectOutbound 的 tag(IPC 查询用)
|
||
private var activeOutboundTag: String = "auto"
|
||
|
||
// ── NEPacketTunnelProvider 生命周期 ──────────────────────────
|
||
|
||
override func startTunnel(
|
||
options: [String: NSObject]?,
|
||
completionHandler: @escaping (Error?) -> Void
|
||
) {
|
||
os_log("PacketTunnelProvider: startTunnel begin", log: log, type: .info)
|
||
stopLock.lock()
|
||
isStopped = false
|
||
stopLock.unlock()
|
||
|
||
// 1. 取 configJson(从 options 或 App Group UserDefaults 回退)
|
||
let configJson: String
|
||
if let opt = options?["config"] as? String, !opt.isEmpty {
|
||
configJson = opt
|
||
} else if let shared = UserDefaults(suiteName: appGroup),
|
||
let cached = shared.string(forKey: "lastConfigJson"),
|
||
!cached.isEmpty {
|
||
configJson = cached
|
||
os_log("PacketTunnelProvider: 从 App Group 读取缓存 configJson", log: log, type: .info)
|
||
} else {
|
||
os_log("PacketTunnelProvider: 无 configJson,使用内置最小配置", log: log, type: .error)
|
||
configJson = minimalFallbackConfig()
|
||
}
|
||
|
||
// 缓存到 App Group,供下次 NE 重启恢复
|
||
if let shared = UserDefaults(suiteName: appGroup) {
|
||
shared.set(configJson, forKey: "lastConfigJson")
|
||
}
|
||
|
||
// 2. 解析网络参数(优先从 JSON 读 tun.address,失败走默认值)
|
||
let tunAddress = parseTunAddress(from: configJson) ?? defaultTunAddr
|
||
let dnsServers = parseDNSServers(from: configJson) ?? [defaultDNS]
|
||
|
||
// 3. 配置 NEPacketTunnelNetworkSettings
|
||
let settings = buildNetworkSettings(tunAddress: tunAddress, dnsServers: dnsServers)
|
||
|
||
setTunnelNetworkSettings(settings) { [weak self] error in
|
||
guard let self = self else { return }
|
||
if let error = error {
|
||
os_log("PacketTunnelProvider: setTunnelNetworkSettings 失败: %{public}@",
|
||
self.log, type: .error, error.localizedDescription)
|
||
completionHandler(error)
|
||
return
|
||
}
|
||
os_log("PacketTunnelProvider: 网络设置已应用", self.log, type: .info)
|
||
|
||
// 4. 建立 socketpair TUN 桥
|
||
do {
|
||
try self.setupTunBridge()
|
||
} catch {
|
||
os_log("PacketTunnelProvider: socketpair 失败: %{public}@",
|
||
self.log, type: .error, error.localizedDescription)
|
||
completionHandler(error)
|
||
return
|
||
}
|
||
|
||
// 5. 初始化 & 启动 libbox 内核
|
||
do {
|
||
try self.startLibbox(configJson: configJson)
|
||
} catch {
|
||
os_log("PacketTunnelProvider: libbox 启动失败: %{public}@",
|
||
self.log, type: .error, error.localizedDescription)
|
||
completionHandler(error)
|
||
return
|
||
}
|
||
|
||
// 6. 启动包流桥接
|
||
self.startPacketBridging()
|
||
|
||
// 7. 启动内存监控
|
||
self.memoryMonitor.start()
|
||
|
||
os_log("PacketTunnelProvider: 隧道已就绪", self.log, type: .info)
|
||
completionHandler(nil)
|
||
}
|
||
}
|
||
|
||
override func stopTunnel(
|
||
with reason: NEProviderStopReason,
|
||
completionHandler: @escaping () -> Void
|
||
) {
|
||
os_log("PacketTunnelProvider: stopTunnel reason=%d", log: log, type: .info, reason.rawValue)
|
||
stopLock.lock()
|
||
isStopped = true
|
||
stopLock.unlock()
|
||
|
||
// 停止内存监控,输出峰值摘要
|
||
memoryMonitor.stop()
|
||
os_log("PacketTunnelProvider: 内存峰值摘要: %{public}@",
|
||
log, type: .info, memoryMonitor.summaryString())
|
||
|
||
// 停止 libbox 内核
|
||
stopLibbox()
|
||
|
||
// 关闭 socketpair
|
||
closeTunBridge()
|
||
|
||
completionHandler()
|
||
}
|
||
|
||
override func handleAppMessage(
|
||
_ messageData: Data,
|
||
completionHandler: ((Data?) -> Void)?
|
||
) {
|
||
guard let request = try? JSONSerialization.jsonObject(with: messageData) as? [String: Any],
|
||
let action = request["action"] as? String else {
|
||
os_log("PacketTunnelProvider: handleAppMessage 无法解析 JSON",
|
||
log: log, type: .error)
|
||
completionHandler?(nil)
|
||
return
|
||
}
|
||
|
||
os_log("PacketTunnelProvider: IPC action=%{public}@", log: log, type: .debug, action)
|
||
|
||
var response: [String: Any] = [:]
|
||
|
||
switch action {
|
||
|
||
case "getStatus":
|
||
// libbox 内核运行 → "on";否则 "error"
|
||
let running: Bool
|
||
#if canImport(Libbox)
|
||
running = libboxService != nil
|
||
#else
|
||
running = false
|
||
#endif
|
||
response["status"] = running ? "on" : "error"
|
||
|
||
case "getMemory":
|
||
// 返回当前内存打点摘要(供主 App 展示内存状态)
|
||
response["memory"] = memoryMonitor.summaryString()
|
||
response["peakRssBytes"] = memoryMonitor.peakResidentBytes
|
||
response["minAvailableBytes"] = memoryMonitor.minAvailableBytes
|
||
response["sampleCount"] = memoryMonitor.sampleCount
|
||
|
||
case "selectOutbound":
|
||
// 切换出口节点 tag(转发给 libbox Clash API)
|
||
let tag = request["tag"] as? String ?? "auto"
|
||
activeOutboundTag = tag
|
||
selectLibboxOutbound(tag: tag)
|
||
response["activeOutbound"] = tag
|
||
|
||
case "getActiveOutbound":
|
||
response["activeOutbound"] = activeOutboundTag
|
||
|
||
default:
|
||
response["error"] = "unknown action: \(action)"
|
||
}
|
||
|
||
if let data = try? JSONSerialization.data(withJSONObject: response) {
|
||
completionHandler?(data)
|
||
} else {
|
||
completionHandler?(nil)
|
||
}
|
||
}
|
||
|
||
override func sleep(completionHandler: @escaping () -> Void) {
|
||
os_log("PacketTunnelProvider: sleep(挂起流量处理)", log: log, type: .debug)
|
||
memoryMonitor.stop()
|
||
completionHandler()
|
||
}
|
||
|
||
override func wake() {
|
||
os_log("PacketTunnelProvider: wake(恢复流量处理)", log: log, type: .debug)
|
||
memoryMonitor.start()
|
||
}
|
||
|
||
// MARK: – 网络设置构建
|
||
|
||
private func buildNetworkSettings(
|
||
tunAddress: String,
|
||
dnsServers: [String]
|
||
) -> NEPacketTunnelNetworkSettings {
|
||
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
||
|
||
// IPv4 设置(全量路由:默认路由 0.0.0.0/0)
|
||
let ipv4 = NEIPv4Settings(addresses: [tunAddress], subnetMasks: [defaultTunMask])
|
||
ipv4.includedRoutes = [NEIPv4Route.default()]
|
||
ipv4.excludedRoutes = []
|
||
settings.ipv4Settings = ipv4
|
||
|
||
// DNS(由 sing-box fake-ip/tun DNS 接管)
|
||
let dns = NEDNSSettings(servers: dnsServers)
|
||
dns.matchDomains = [""] // 拦截所有域名 DNS
|
||
settings.dnsSettings = dns
|
||
|
||
settings.mtu = tunMTU
|
||
return settings
|
||
}
|
||
|
||
// MARK: – TUN 桥(socketpair)
|
||
|
||
private func setupTunBridge() throws {
|
||
var fds: [Int32] = [-1, -1]
|
||
// AF_UNIX SOCK_DGRAM:每次 read/write 保留消息边界,适合 IP 包
|
||
let ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds)
|
||
guard ret == 0 else {
|
||
throw makePosixError("socketpair 失败")
|
||
}
|
||
// 提升 socket 缓冲区到 4 MB(避免大流量丢包)
|
||
let bufSize: Int32 = 4 * 1024 * 1024
|
||
setsockopt(fds[0], SOL_SOCKET, SO_SNDBUF, &bufSize, socklen_t(MemoryLayout<Int32>.size))
|
||
setsockopt(fds[0], SOL_SOCKET, SO_RCVBUF, &bufSize, socklen_t(MemoryLayout<Int32>.size))
|
||
setsockopt(fds[1], SOL_SOCKET, SO_SNDBUF, &bufSize, socklen_t(MemoryLayout<Int32>.size))
|
||
setsockopt(fds[1], SOL_SOCKET, SO_RCVBUF, &bufSize, socklen_t(MemoryLayout<Int32>.size))
|
||
tunFDs = (ne: fds[0], libbox: fds[1])
|
||
os_log("PacketTunnelProvider: socketpair 创建成功 ne=%d libbox=%d",
|
||
log: log, type: .debug, fds[0], fds[1])
|
||
}
|
||
|
||
private func closeTunBridge() {
|
||
if tunFDs.ne >= 0 {
|
||
close(tunFDs.ne)
|
||
tunFDs.ne = -1
|
||
}
|
||
if tunFDs.libbox >= 0 {
|
||
close(tunFDs.libbox)
|
||
tunFDs.libbox = -1
|
||
}
|
||
}
|
||
|
||
// MARK: – 包流桥接(packetFlow ↔ socketpair ↔ libbox)
|
||
|
||
private func startPacketBridging() {
|
||
let neFD = tunFDs.ne
|
||
guard neFD >= 0 else { return }
|
||
|
||
// Outgoing(App → 外网):packetFlow → write to neFD → libbox 读
|
||
readFromPacketFlow(neFD: neFD)
|
||
|
||
// Incoming(外网 → App):libbox 写 → read from neFD → packetFlow
|
||
writeToPacketFlow(neFD: neFD)
|
||
}
|
||
|
||
/// 从 NE packetFlow 读出出站包,写入 socketpair NE 侧(libbox 从另一侧读)。
|
||
private func readFromPacketFlow(neFD: Int32) {
|
||
packetFlow.readPacketObjects { [weak self] packets in
|
||
guard let self = self else { return }
|
||
self.stopLock.lock()
|
||
let stopped = self.isStopped
|
||
self.stopLock.unlock()
|
||
guard !stopped else { return }
|
||
|
||
for packet in packets {
|
||
let data = packet.data
|
||
_ = data.withUnsafeBytes { ptr -> Int in
|
||
guard let base = ptr.baseAddress else { return 0 }
|
||
return write(neFD, base, data.count)
|
||
}
|
||
}
|
||
// 尾递归:持续读取
|
||
self.readFromPacketFlow(neFD: neFD)
|
||
}
|
||
}
|
||
|
||
/// 从 socketpair NE 侧读 libbox 写入的入站包,写回 NE packetFlow。
|
||
private func writeToPacketFlow(neFD: Int32) {
|
||
let bufSize = 65536
|
||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||
guard let self = self else { return }
|
||
var buffer = [UInt8](repeating: 0, count: bufSize)
|
||
while true {
|
||
self.stopLock.lock()
|
||
let stopped = self.isStopped
|
||
self.stopLock.unlock()
|
||
guard !stopped else { break }
|
||
|
||
let n = read(neFD, &buffer, bufSize)
|
||
if n <= 0 {
|
||
if errno == EINTR { continue }
|
||
break
|
||
}
|
||
let data = Data(buffer[..<n])
|
||
// 根据 IP 版本首字节判断协议族
|
||
let version = (buffer[0] & 0xF0) >> 4
|
||
let proto: NSNumber = version == 6
|
||
? NSNumber(value: AF_INET6)
|
||
: NSNumber(value: AF_INET)
|
||
self.packetFlow.writePackets([data], withProtocols: [proto])
|
||
}
|
||
os_log("PacketTunnelProvider: writeToPacketFlow 退出循环", self.log, type: .debug)
|
||
}
|
||
}
|
||
|
||
// MARK: – libbox 生命周期
|
||
|
||
private func startLibbox(configJson: String) throws {
|
||
#if canImport(Libbox)
|
||
var serviceError: NSError? = nil
|
||
let svc = LibboxNewService(configJson, self, false, &serviceError)
|
||
if let err = serviceError { throw err }
|
||
guard let svc else {
|
||
throw makeError("LibboxNewService 返回 nil")
|
||
}
|
||
var startError: NSError? = nil
|
||
svc.start(&startError)
|
||
if let err = startError { throw err }
|
||
libboxService = svc
|
||
os_log("PacketTunnelProvider: libbox 内核已启动", log: log, type: .info)
|
||
#else
|
||
os_log("PacketTunnelProvider: Libbox 未链接,骨架模式运行", log: log, type: .fault)
|
||
// 骨架模式:不抛错,让 NE 连接进入 "on" 状态(用于 UI 验收)
|
||
#endif
|
||
}
|
||
|
||
private func stopLibbox() {
|
||
#if canImport(Libbox)
|
||
guard let svc = libboxService else { return }
|
||
var stopError: NSError? = nil
|
||
svc.stop(&stopError)
|
||
if let err = stopError {
|
||
os_log("PacketTunnelProvider: libbox stop 错误: %{public}@",
|
||
log: log, type: .error, err.localizedDescription)
|
||
}
|
||
libboxService = nil
|
||
os_log("PacketTunnelProvider: libbox 内核已停止", log: log, type: .info)
|
||
#endif
|
||
}
|
||
|
||
private func selectLibboxOutbound(tag: String) {
|
||
#if canImport(Libbox)
|
||
// sing-box Clash API selectOutbound(通过 libbox 内部 HTTP 调用)
|
||
// libbox 暴露了 LibboxClashServer 或类似接口;此处记录并由内核异步处理。
|
||
os_log("PacketTunnelProvider: selectOutbound tag=%{public}@", log: log, type: .info, tag)
|
||
// TODO(11G): 调用 libboxService 的 outbound selector API(sing-box v1.13+ 支持)
|
||
#endif
|
||
}
|
||
|
||
// MARK: – 配置解析工具
|
||
|
||
/// 从 configJson 中提取 tun.inet4_address 第一条(如 "172.19.0.1/30")。
|
||
private func parseTunAddress(from json: String) -> String? {
|
||
guard let data = json.data(using: .utf8),
|
||
let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||
let tun = root["tun"] as? [String: Any] else { return nil }
|
||
|
||
// 兼容 inet4_address 字符串或数组
|
||
if let addrStr = tun["inet4_address"] as? String {
|
||
return addrStr.components(separatedBy: "/").first
|
||
}
|
||
if let addrArr = tun["inet4_address"] as? [String],
|
||
let first = addrArr.first {
|
||
return first.components(separatedBy: "/").first
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/// 从 configJson 中提取 dns.servers 数组(取 address 字段)。
|
||
private func parseDNSServers(from json: String) -> [String]? {
|
||
guard let data = json.data(using: .utf8),
|
||
let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||
let dns = root["dns"] as? [String: Any],
|
||
let servers = dns["servers"] as? [[String: Any]] else { return nil }
|
||
|
||
let addrs = servers.compactMap { $0["address"] as? String }
|
||
return addrs.isEmpty ? nil : addrs
|
||
}
|
||
|
||
/// 极简内置配置(在完全没有外部配置时的最后回退,只做 pass-through 测试用)。
|
||
private func minimalFallbackConfig() -> String {
|
||
return """
|
||
{
|
||
"log": {"level": "info"},
|
||
"dns": {"servers": [{"address": "\(defaultDNS)"}]},
|
||
"inbounds": [],
|
||
"outbounds": [{"type": "direct", "tag": "direct"}],
|
||
"tun": {
|
||
"enabled": true,
|
||
"inet4_address": "\(defaultTunAddr)/24",
|
||
"auto_route": true,
|
||
"mtu": 1500
|
||
}
|
||
}
|
||
"""
|
||
}
|
||
|
||
// MARK: – 错误工具
|
||
|
||
private func makeError(_ message: String) -> NSError {
|
||
NSError(domain: "PacketTunnelProvider", code: -1,
|
||
userInfo: [NSLocalizedDescriptionKey: message])
|
||
}
|
||
|
||
private func makePosixError(_ prefix: String) -> NSError {
|
||
NSError(domain: NSPOSIXErrorDomain, code: Int(errno),
|
||
userInfo: [NSLocalizedDescriptionKey: "\(prefix): \(String(cString: strerror(errno)))"])
|
||
}
|
||
}
|
||
|
||
// MARK: – LibboxPlatformInterface 协议实现
|
||
// 仅在 Libbox.xcframework 链入时编译。
|
||
// ObjC 协议名:LibboxPlatformInterface(gomobile 生成,见 experimental/libbox)。
|
||
|
||
#if canImport(Libbox)
|
||
extension PacketTunnelProvider: LibboxPlatformInterface {
|
||
|
||
/// 接口控制(SO_MARK / setsockopt 等保护套接字不走隧道)。
|
||
/// iOS NE 环境无需手动设置(系统自动处理),实现为 no-op。
|
||
func autoDetectInterfaceControl(_ fd: Int32) throws {
|
||
// NE 自动处理接口路由,无需 SO_MARK
|
||
}
|
||
|
||
/// 进程信息查询(供 sing-box 记录连接来源进程)。
|
||
/// NE 沙盒环境无权访问其他进程信息,返回 nil。
|
||
func findProcessInfo(
|
||
_ network: String?,
|
||
source: String?,
|
||
destination: String?,
|
||
uid: Int32
|
||
) throws -> LibboxProcessInfo? {
|
||
return nil
|
||
}
|
||
|
||
/// 创建 TUN 接口并返回 fd。
|
||
/// 在 NE 环境中:
|
||
/// 1. 从 LibboxTunOptions 读取网络参数(覆盖 startTunnel 中的默认值)。
|
||
/// 2. 将 libbox 侧 fd 返回给内核(内核通过 read/write 此 fd 收发 IP 包)。
|
||
/// 3. NE 侧 fd 用于 packetFlow 桥接循环。
|
||
func openTun(_ options: LibboxTunOptions?) throws -> Int32 {
|
||
os_log("PacketTunnelProvider: openTun called (mtu=%d)", log: log, type: .info,
|
||
options?.mtu ?? 0)
|
||
guard tunFDs.libbox >= 0 else {
|
||
throw makeError("openTun: socketpair 未初始化")
|
||
}
|
||
return tunFDs.libbox
|
||
}
|
||
|
||
/// 不使用 /proc/net(iOS 无 procfs)。
|
||
func useProcFS() -> Bool { return false }
|
||
|
||
/// libbox 日志输出(转 os.log)。
|
||
func writeLog(_ message: String?) {
|
||
guard let msg = message, !msg.isEmpty else { return }
|
||
os_log("libbox: %{public}@", log: log, type: .debug, msg)
|
||
}
|
||
|
||
/// 清除 DNS 缓存(iOS 无系统 DNS 缓存接口,no-op)。
|
||
func clearDNSCache() { }
|
||
}
|
||
#endif
|