diag(client/macos): 抓 sing-box urltest 内核日志,定位探测为何失败

确认 urltest 为空是内核探测 100% 失败(传输/读取链路均正常)。本次注入 log.level=debug,
StatsCollector 新增 Log 命令客户端,抓取内核日志里 urltest/gstatic 相关行(失败原因:
DNS/连接/超时),塞进 stats 的 klog 字段经主 app 可读日志透出 —— 直接看到内核报错。

CURRENT_PROJECT_VERSION 51→52。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 14:58:07 +08:00
parent 211fda37e3
commit e3fb5239a6
3 changed files with 36 additions and 15 deletions
@@ -130,6 +130,11 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
experimental["cache_file"] = ["enabled": true]
}
root["experimental"] = experimental
// : debug , urltest (DNS//),
// StatsCollector Log
var logCfg = (root["log"] as? [String: Any]) ?? [:]
logCfg["level"] = "debug"
root["log"] = logCfg
guard let data = try? JSONSerialization.data(withJSONObject: root),
let out = String(data: data, encoding: .utf8) else { return configJSON }
return out
@@ -374,6 +379,7 @@ private func simpleError(_ msg: String) -> NSError {
final class StatsCollector: NSObject {
private let queue = DispatchQueue(label: "pangolin.stats.collector")
private var statusClient: LibboxCommandClient?
private var logClient: LibboxCommandClient?
private var started = false
private var clashTimer: DispatchSourceTimer?
@@ -396,23 +402,24 @@ final class StatsCollector: NSObject {
guard let self, !self.started else { return }
self.clashBase = "http://127.0.0.1:\(clashPort)"
self.clashSecret = clashSecret
self.statusClient = self.connectStatusClient()
self.statusClient = self.connectCommand(LibboxCommandStatus, "status")
self.logClient = self.connectCommand(LibboxCommandLog, "log") // urltest
self.started = true
self.startClashTimer()
}
}
/// libbox Status client()command.sock ,
private func connectStatusClient() -> LibboxCommandClient? {
/// libbox client command.sock ,
private func connectCommand(_ command: Int32, _ label: String) -> LibboxCommandClient? {
let options = LibboxCommandClientOptions()
options.statusInterval = 1_000_000_000 // 1s
options.addCommand(LibboxCommandStatus)
options.addCommand(command)
guard let c = LibboxNewCommandClient(self, options) else { return nil }
for _ in 0..<20 {
do { try c.connect(); NSLog("[pangolin/collector] status connected"); return c }
do { try c.connect(); NSLog("[pangolin/collector] %@ connected", label); return c }
catch { Thread.sleep(forTimeInterval: 0.25) }
}
NSLog("[pangolin/collector] status gave up")
NSLog("[pangolin/collector] %@ gave up", label)
return nil
}
@@ -421,7 +428,9 @@ final class StatsCollector: NSObject {
guard let self else { return }
self.clashTimer?.cancel(); self.clashTimer = nil
if let c = self.statusClient { try? c.disconnect() }
if let c = self.logClient { try? c.disconnect() }
self.statusClient = nil
self.logClient = nil
self.started = false
self.lock.lock()
self.latest = StatsCollector.zero
@@ -564,7 +573,19 @@ extension StatsCollector: LibboxCommandClientHandlerProtocol {
func connected() {}
func disconnected(_ message: String?) {}
func clearLogs() {}
func writeLogs(_ messageList: (any LibboxLogIteratorProtocol)?) {}
func writeLogs(_ messageList: (any LibboxLogIteratorProtocol)?) {
guard let it = messageList else { return }
while it.hasNext() {
guard let entry = it.next() else { break }
let msg = entry.message
let low = msg.lowercased()
// urltest (:DNS//)
guard low.contains("urltest") || low.contains("url-test")
|| low.contains("gstatic") || low.contains("generate_204") else { continue }
NSLog("[pangolin/klog] %@", msg)
lock.lock(); latest["klog"] = String(msg.prefix(180)); lock.unlock()
}
}
func setDefaultLogLevel(_ level: Int32) {}
func initializeClashMode(_ modeList: (any LibboxStringIteratorProtocol)?, currentMode: String?) {}
func updateClashMode(_ newMode: String?) {}
@@ -659,7 +659,7 @@
baseConfigurationReference = C57BBFD43F9175D1E23685EF /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 51;
CURRENT_PROJECT_VERSION = 52;
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 = 51;
CURRENT_PROJECT_VERSION = 52;
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 = 51;
CURRENT_PROJECT_VERSION = 52;
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 = 51;
CURRENT_PROJECT_VERSION = 52;
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 = 51;
CURRENT_PROJECT_VERSION = 52;
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 = 51;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = BYL4KQHMTN;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
+2 -2
View File
@@ -67,9 +67,9 @@ final class StatsClient: NSObject {
if self.diagTick % 5 == 0 {
let urls = (obj["urltestResults"] as? [[String: Any]]) ?? []
let pairs = urls.map { "\($0["tag"] ?? "?")=\($0["delayMs"] ?? -1)" }.joined(separator: ",")
NSLog("[pangolin/stats] dl=%@ ul=%@ urltest[%d]=%@ diag=%@",
NSLog("[pangolin/stats] dl=%@ ul=%@ urltest[%d]=%@ diag=%@ klog=%@",
"\(obj["downloadSpeed"] ?? 0)", "\(obj["uploadSpeed"] ?? 0)", urls.count, pairs,
"\(obj["diag"] ?? "-")")
"\(obj["diag"] ?? "-")", "\(obj["klog"] ?? "-")")
}
self.emit(obj)
}