fix(client/macos): sysext 把 command.sock 建在登录用户容器并 chown,修实时统计断流
根因:系统扩展以 root 运行,其 App Group 容器是 /var/root/Library/Group Containers/..., 而用户 app 的是 /Users/<user>/...(容器 home 相对)。libbox 把 command.sock 建在 basePath 下 → 落在 root 容器;无 root 权限的 app 永远 connect 不上 → 连接页 — KB/s + 延迟不刷新。 (经 sudo ls 实测确认:socket 在 /var/root 容器,用户容器里没有。) - PacketTunnelProvider 用 SCDynamicStoreCopyConsoleUser 取登录用户,把 libbox basePath/ workingPath 指向该用户的同名容器(root 可写),command.sock 即落在 app 查找的路径。 - server.start() 后 chown 容器/work/command.sock 给登录用户:Unix socket connect() 需写 权限,仅 owner 满足,否则 app 仍 EACCES。拿不到登录用户时回退 root 容器(隧道可用、仅统计不可用)。 - sysext 代码改动 → CURRENT_PROJECT_VERSION 43→44,让 sysextd 重装新扩展。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import Libbox
|
||||
import Network
|
||||
import NetworkExtension
|
||||
import os
|
||||
import SystemConfiguration
|
||||
|
||||
private let log = Logger(subsystem: "com.pangolin.pangolin.PacketTunnel", category: "provider")
|
||||
// macOS 原生 App Group 格式 <TeamID>.<name>(非 iOS 的 group. 前缀)。
|
||||
@@ -34,8 +35,24 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
do {
|
||||
let configContent = try self.resolveConfig(options)
|
||||
|
||||
guard let base = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
|
||||
// ⚠️ 系统扩展以 root 运行,其 App Group 容器是 /var/root/Library/Group
|
||||
// Containers/...,与用户 app 的 /Users/<user>/... 不是同一路径(容器是 home
|
||||
// 相对的)。libbox 把 command.sock 建在 basePath 下 → 落在 root 容器,无 root
|
||||
// 权限的 app 永远 connect 不上 → 实时统计断流(— KB/s + 延迟不刷新)。
|
||||
// 解决:把 libbox 数据目录指向「当前登录用户」的同名容器(root 可写),并在
|
||||
// 建好 socket 后 chown 给该用户,使 app 能 connect。拿不到登录用户时回退到
|
||||
// root 容器(至少隧道能跑,仅统计不可用)。
|
||||
let consoleUser = Self.consoleUser()
|
||||
let base: URL
|
||||
if let u = consoleUser {
|
||||
base = u.home
|
||||
.appendingPathComponent("Library/Group Containers/\(appGroup)", isDirectory: true)
|
||||
log.info("libbox base → 登录用户容器 \(base.path, privacy: .public)")
|
||||
} else if let root = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: appGroup) {
|
||||
base = root
|
||||
log.error("拿不到登录用户,回退 root 容器 \(base.path, privacy: .public)(app 连不上 command.sock)")
|
||||
} else {
|
||||
throw simpleError("no app group container")
|
||||
}
|
||||
let work = base.appendingPathComponent("work", isDirectory: true)
|
||||
@@ -57,6 +74,16 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
throw newErr ?? simpleError("LibboxNewCommandServer returned nil")
|
||||
}
|
||||
try server.start()
|
||||
|
||||
// server.start() 已在 base 下建出 command.sock(root 拥有)。Unix socket 的
|
||||
// connect() 需要 socket 文件的写权限,仅 owner 满足 → 把容器/work/socket 都
|
||||
// chown 给登录用户,让无 root 的 app 能连。root 在用户 home 里建的文件默认 root
|
||||
// 拥有,不 chown 则 app connect 被 EACCES 拒。
|
||||
if let u = consoleUser {
|
||||
chown(base.path, u.uid, u.gid)
|
||||
chown(work.path, u.uid, u.gid)
|
||||
chown(base.appendingPathComponent("command.sock").path, u.uid, u.gid)
|
||||
}
|
||||
// 必须传非空 options:此版本 libbox 的 StartOrReloadService 会解引用 options,
|
||||
// 传 nil 会在 command_server.go:175 触发 SIGSEGV(空指针)。
|
||||
try server.startOrReloadService(configContent, options: LibboxOverrideOptions())
|
||||
@@ -87,6 +114,23 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
completionHandler?(nil)
|
||||
}
|
||||
|
||||
// 当前图形界面登录用户(GUI console user)及其 home。系统扩展据此把 libbox 数据目录
|
||||
// 指向用户容器,而非 root(/var/root)容器。拿不到(无人登录/登录窗)返回 nil。
|
||||
private static func consoleUser() -> (uid: uid_t, gid: gid_t, home: URL)? {
|
||||
guard let store = SCDynamicStoreCreate(nil, "com.pangolin.pangolin.PacketTunnel" as CFString, nil, nil) else {
|
||||
return nil
|
||||
}
|
||||
var uid: uid_t = 0
|
||||
var gid: gid_t = 0
|
||||
guard let name = SCDynamicStoreCopyConsoleUser(store, &uid, &gid) as String?,
|
||||
!name.isEmpty, name != "loginwindow", uid >= 500,
|
||||
let pw = getpwuid(uid) else {
|
||||
return nil
|
||||
}
|
||||
let home = URL(fileURLWithPath: String(cString: pw.pointee.pw_dir), isDirectory: true)
|
||||
return (uid, gid, home)
|
||||
}
|
||||
|
||||
private func resolveConfig(_ options: [String: NSObject]?) throws -> String {
|
||||
if let inline = options?["configContent"] as? String, !inline.isEmpty {
|
||||
return inline
|
||||
|
||||
@@ -659,7 +659,7 @@
|
||||
baseConfigurationReference = C57BBFD43F9175D1E23685EF /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
|
||||
@@ -674,7 +674,7 @@
|
||||
baseConfigurationReference = F8904897A48DC81799B8752E /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
|
||||
@@ -689,7 +689,7 @@
|
||||
baseConfigurationReference = B21E68FC1F5D33DD67A0DF5E /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
|
||||
@@ -960,7 +960,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
DEVELOPMENT_TEAM = BYL4KQHMTN;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -1010,7 +1010,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
DEVELOPMENT_TEAM = BYL4KQHMTN;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -1059,7 +1059,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 43;
|
||||
CURRENT_PROJECT_VERSION = 44;
|
||||
DEVELOPMENT_TEAM = BYL4KQHMTN;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
||||
Reference in New Issue
Block a user