Tools View & Crash Report & OOM Report
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
@@ -182,6 +183,12 @@ public struct ImportRemoteProfileRequest: Hashable, Identifiable {
|
||||
@MainActor
|
||||
public class ExtensionEnvironments: ObservableObject {
|
||||
@Published public var commandClient = CommandClient([.log, .status, .groups, .clashMode])
|
||||
public let crashReportManager = CrashReportManager()
|
||||
public let oomReportManager = OOMReportManager()
|
||||
public var totalUnreadReportCount: Int {
|
||||
crashReportManager.unreadCount + oomReportManager.unreadCount
|
||||
}
|
||||
|
||||
@Published public var extensionProfileLoading = true
|
||||
@Published public var extensionProfile: ExtensionProfile?
|
||||
@Published public var emptyProfiles = false
|
||||
@@ -193,8 +200,19 @@ public class ExtensionEnvironments: ObservableObject {
|
||||
public let profileUpdate = ObjectWillChangePublisher()
|
||||
public let selectedProfileUpdate = ObjectWillChangePublisher()
|
||||
public let openSettings = ObjectWillChangePublisher()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
public init() {
|
||||
crashReportManager.objectWillChange
|
||||
.sink { [weak self] _ in
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
oomReportManager.objectWillChange
|
||||
.sink { [weak self] _ in
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
if Variant.screenshotMode {
|
||||
extensionProfileLoading = false
|
||||
extensionProfile = .mock
|
||||
@@ -205,6 +223,8 @@ public class ExtensionEnvironments: ObservableObject {
|
||||
public func postReload() {
|
||||
Task {
|
||||
await reload()
|
||||
await crashReportManager.refresh()
|
||||
await oomReportManager.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import NetworkExtension
|
||||
import os
|
||||
import UserNotifications
|
||||
#if os(macOS)
|
||||
import CoreWLAN
|
||||
#endif
|
||||
|
||||
public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtocol, LibboxCommandServerHandlerProtocol {
|
||||
private static let logger = Logger(category: "ExtensionPlatformInterface")
|
||||
private let tunnel: ExtensionProvider
|
||||
private var networkSettings: NEPacketTunnelNetworkSettings?
|
||||
|
||||
@@ -449,11 +451,17 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc
|
||||
}
|
||||
}
|
||||
|
||||
public func triggerNativeCrash() throws {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(200)) {
|
||||
fatalError("debug native crash")
|
||||
}
|
||||
}
|
||||
|
||||
public func writeDebugMessage(_ message: String?) {
|
||||
guard let message else {
|
||||
return
|
||||
}
|
||||
tunnel.writeMessage(message)
|
||||
Self.logger.debug("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
||||
@@ -216,8 +216,10 @@ public class ExtensionProfile: ObservableObject {
|
||||
let configContent = try await profile.readAsync()
|
||||
options["configContent"] = NSString(string: configContent)
|
||||
|
||||
#if !os(macOS)
|
||||
options["ignoreMemoryLimit"] = await NSNumber(value: SharedPreferences.ignoreMemoryLimit.get())
|
||||
#if os(macOS)
|
||||
options["oomKillerEnabled"] = await NSNumber(value: SharedPreferences.oomKillerEnabled.get())
|
||||
options["oomMemoryLimitMB"] = await NSNumber(value: SharedPreferences.oomMemoryLimitMB.get())
|
||||
options["oomKillerKillConnections"] = await NSNumber(value: SharedPreferences.oomKillerKillConnections.get())
|
||||
#endif
|
||||
options["systemProxyEnabled"] = await NSNumber(value: SharedPreferences.systemProxyEnabled.get())
|
||||
options["excludeDefaultRoute"] = await NSNumber(value: SharedPreferences.excludeDefaultRoute.get())
|
||||
|
||||
@@ -80,6 +80,22 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
private var locationDelegate: stubLocationDelegate?
|
||||
#endif
|
||||
|
||||
override public init() {
|
||||
#if os(macOS)
|
||||
if Variant.useSystemExtension {
|
||||
NativeCrashReporter.installForCurrentProcess(
|
||||
basePath: FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent("NativeCrash")
|
||||
)
|
||||
} else {
|
||||
NativeCrashReporter.installForCurrentProcess()
|
||||
}
|
||||
#else
|
||||
NativeCrashReporter.installForCurrentProcess()
|
||||
#endif
|
||||
super.init()
|
||||
}
|
||||
|
||||
override open func startTunnel(options startOptions: [String: NSObject]?) async throws {
|
||||
let basePath: String
|
||||
let workingPath: String
|
||||
@@ -132,6 +148,8 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
options.tempPath = tempPath
|
||||
|
||||
options.logMaxLines = 3000
|
||||
options.debug = SharedPreferences.inDebug
|
||||
options.crashReportSource = "NetworkExtension"
|
||||
|
||||
#if os(tvOS)
|
||||
if let port = effectiveOptions["commandServerPort"] as? NSNumber {
|
||||
@@ -142,24 +160,21 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
options.oomKillerEnabled = (effectiveOptions["oomKillerEnabled"] as? NSNumber)?.boolValue ?? false
|
||||
let oomMemoryLimitMB = (effectiveOptions["oomMemoryLimitMB"] as? NSNumber)?.int64Value ?? 0
|
||||
options.oomMemoryLimit = oomMemoryLimitMB * 1024 * 1024
|
||||
options.oomKillerDisabled = !((effectiveOptions["oomKillerKillConnections"] as? NSNumber)?.boolValue ?? false)
|
||||
#else
|
||||
options.oomKillerEnabled = true
|
||||
#endif
|
||||
|
||||
var setupError: NSError?
|
||||
LibboxSetup(options, &setupError)
|
||||
if let setupError {
|
||||
throw ExtensionStartupError("(packet-tunnel) error: setup service: \(setupError.localizedDescription)")
|
||||
}
|
||||
|
||||
let stderrPath = URL(fileURLWithPath: tempPath, isDirectory: true).appendingPathComponent("stderr.log").path
|
||||
var stderrError: NSError?
|
||||
LibboxRedirectStderr(stderrPath, &stderrError)
|
||||
if let stderrError {
|
||||
throw ExtensionStartupError("(packet-tunnel) redirect stderr error: \(stderrError.localizedDescription)")
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
let ignoreMemoryLimit = (effectiveOptions["ignoreMemoryLimit"] as? NSNumber)?.boolValue ?? false
|
||||
LibboxSetMemoryLimit(!ignoreMemoryLimit)
|
||||
#endif
|
||||
|
||||
var error: NSError?
|
||||
commandServer = LibboxNewCommandServer(platformInterface, platformInterface, &error)
|
||||
if let error {
|
||||
@@ -179,7 +194,6 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
#endif
|
||||
|
||||
writeMessage("(packet-tunnel): Here I stand")
|
||||
do {
|
||||
try await startService()
|
||||
} catch {
|
||||
@@ -190,6 +204,7 @@ open class ExtensionProvider: NEPacketTunnelProvider {
|
||||
#endif
|
||||
throw error
|
||||
}
|
||||
writeMessage("(packet-tunnel): Here I stand")
|
||||
#if os(macOS)
|
||||
if Variant.useSystemExtension {
|
||||
xpcService.markServiceReady()
|
||||
|
||||
@@ -60,6 +60,121 @@
|
||||
}
|
||||
}
|
||||
|
||||
@objc(CrashLogFileResult) public class CrashLogFileResult: NSObject, NSSecureCoding {
|
||||
public static let supportsSecureCoding = true
|
||||
|
||||
@objc public var fileName: String
|
||||
@objc public var content: String
|
||||
@objc public var modificationDate: Date
|
||||
|
||||
public init(fileName: String, content: String, modificationDate: Date) {
|
||||
self.fileName = fileName
|
||||
self.content = content
|
||||
self.modificationDate = modificationDate
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fileName = coder.decodeObject(of: NSString.self, forKey: "fileName") as? String ?? ""
|
||||
content = coder.decodeObject(of: NSString.self, forKey: "content") as? String ?? ""
|
||||
modificationDate = coder.decodeObject(of: NSDate.self, forKey: "modificationDate") as? Date ?? Date()
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(fileName as NSString, forKey: "fileName")
|
||||
coder.encode(content as NSString, forKey: "content")
|
||||
coder.encode(modificationDate as NSDate, forKey: "modificationDate")
|
||||
}
|
||||
}
|
||||
|
||||
@objc(CrashArtifactsResult) public class CrashArtifactsResult: NSObject, NSSecureCoding {
|
||||
public static let supportsSecureCoding = true
|
||||
|
||||
@objc public var crashLogs: [CrashLogFileResult] = []
|
||||
@objc public var helperNativeCrashData: Data?
|
||||
@objc public var extensionNativeCrashData: Data?
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
let logClasses = [NSArray.self, CrashLogFileResult.self] as [AnyClass]
|
||||
crashLogs = coder.decodeObject(of: logClasses, forKey: "crashLogs") as? [CrashLogFileResult] ?? []
|
||||
helperNativeCrashData = coder.decodeObject(of: NSData.self, forKey: "helperNativeCrashData") as? Data
|
||||
extensionNativeCrashData = coder.decodeObject(of: NSData.self, forKey: "extensionNativeCrashData") as? Data
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(crashLogs as NSArray, forKey: "crashLogs")
|
||||
coder.encode(helperNativeCrashData as NSData?, forKey: "helperNativeCrashData")
|
||||
coder.encode(extensionNativeCrashData as NSData?, forKey: "extensionNativeCrashData")
|
||||
}
|
||||
}
|
||||
|
||||
@objc(OOMReportFileResult) public class OOMReportFileResult: NSObject, NSSecureCoding {
|
||||
public static let supportsSecureCoding = true
|
||||
|
||||
@objc public var name: String
|
||||
@objc public var data: Data
|
||||
|
||||
public init(name: String, data: Data) {
|
||||
self.name = name
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
name = coder.decodeObject(of: NSString.self, forKey: "name") as? String ?? ""
|
||||
data = coder.decodeObject(of: NSData.self, forKey: "data") as? Data ?? Data()
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(name as NSString, forKey: "name")
|
||||
coder.encode(data as NSData, forKey: "data")
|
||||
}
|
||||
}
|
||||
|
||||
@objc(OOMReportDirectoryResult) public class OOMReportDirectoryResult: NSObject, NSSecureCoding {
|
||||
public static let supportsSecureCoding = true
|
||||
|
||||
@objc public var directoryName: String
|
||||
@objc public var files: [OOMReportFileResult] = []
|
||||
|
||||
public init(directoryName: String, files: [OOMReportFileResult]) {
|
||||
self.directoryName = directoryName
|
||||
self.files = files
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
directoryName = coder.decodeObject(of: NSString.self, forKey: "directoryName") as? String ?? ""
|
||||
let fileClasses = [NSArray.self, OOMReportFileResult.self] as [AnyClass]
|
||||
files = coder.decodeObject(of: fileClasses, forKey: "files") as? [OOMReportFileResult] ?? []
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(directoryName as NSString, forKey: "directoryName")
|
||||
coder.encode(files as NSArray, forKey: "files")
|
||||
}
|
||||
}
|
||||
|
||||
@objc(OOMReportArtifactsResult) public class OOMReportArtifactsResult: NSObject, NSSecureCoding {
|
||||
public static let supportsSecureCoding = true
|
||||
|
||||
@objc public var reports: [OOMReportDirectoryResult] = []
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
let reportClasses = [NSArray.self, OOMReportDirectoryResult.self] as [AnyClass]
|
||||
reports = coder.decodeObject(of: reportClasses, forKey: "reports") as? [OOMReportDirectoryResult] ?? []
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(reports as NSArray, forKey: "reports")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public protocol RootHelperProtocol {
|
||||
func findConnectionOwner(
|
||||
ipProtocol: Int32,
|
||||
@@ -76,6 +191,10 @@
|
||||
func startNeighborMonitor(callbackEndpoint: NSXPCListenerEndpoint, reply: @escaping (NSError?) -> Void)
|
||||
func closeNeighborMonitor(reply: @escaping (NSError?) -> Void)
|
||||
func registerMyInterface(name: String, reply: @escaping (NSError?) -> Void)
|
||||
func collectAllCrashArtifacts(reply: @escaping (CrashArtifactsResult?, NSError?) -> Void)
|
||||
func collectOOMReportArtifacts(reply: @escaping (OOMReportArtifactsResult?, NSError?) -> Void)
|
||||
func triggerGoCrash(reply: @escaping (NSError?) -> Void)
|
||||
func triggerNativeCrash(reply: @escaping (NSError?) -> Void)
|
||||
}
|
||||
|
||||
public enum RootHelperXPC {
|
||||
@@ -87,6 +206,24 @@
|
||||
argumentIndex: 0,
|
||||
ofReply: true
|
||||
)
|
||||
let crashArtifactClasses = NSSet(array: [
|
||||
CrashArtifactsResult.self, NSArray.self, CrashLogFileResult.self, NSData.self,
|
||||
]) as! Set<AnyHashable>
|
||||
interface.setClasses(
|
||||
crashArtifactClasses,
|
||||
for: #selector(RootHelperProtocol.collectAllCrashArtifacts(reply:)),
|
||||
argumentIndex: 0,
|
||||
ofReply: true
|
||||
)
|
||||
let oomArtifactClasses = NSSet(array: [
|
||||
OOMReportArtifactsResult.self, NSArray.self, OOMReportDirectoryResult.self, OOMReportFileResult.self, NSData.self,
|
||||
]) as! Set<AnyHashable>
|
||||
interface.setClasses(
|
||||
oomArtifactClasses,
|
||||
for: #selector(RootHelperProtocol.collectOOMReportArtifacts(reply:)),
|
||||
argumentIndex: 0,
|
||||
ofReply: true
|
||||
)
|
||||
let endpointClasses = NSSet(array: [NSXPCListenerEndpoint.self]) as! Set<AnyHashable>
|
||||
interface.setClasses(
|
||||
endpointClasses,
|
||||
@@ -141,10 +278,10 @@
|
||||
return newConnection
|
||||
}
|
||||
|
||||
private func performXPCCall<T>(
|
||||
private func performXPCCallOptional<T>(
|
||||
_ operation: String,
|
||||
call: (RootHelperProtocol, @escaping (T?, NSError?) -> Void) -> Void
|
||||
) throws -> T {
|
||||
) throws -> T? {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: T?
|
||||
var resultError: NSError?
|
||||
@@ -184,13 +321,18 @@
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let value = result else {
|
||||
let error = NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
return result
|
||||
}
|
||||
|
||||
private func performXPCCall<T>(
|
||||
_ operation: String,
|
||||
call: (RootHelperProtocol, @escaping (T?, NSError?) -> Void) -> Void
|
||||
) throws -> T {
|
||||
guard let value: T = try performXPCCallOptional(operation, call: call) else {
|
||||
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "\(operation) returned nil",
|
||||
])
|
||||
throw error
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -287,51 +429,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
public func collectAllCrashArtifacts() throws -> CrashArtifactsResult {
|
||||
try performXPCCall("collectAllCrashArtifacts") { proxy, reply in
|
||||
proxy.collectAllCrashArtifacts(reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
public func collectOOMReportArtifacts() throws -> OOMReportArtifactsResult {
|
||||
try performXPCCall("collectOOMReportArtifacts") { proxy, reply in
|
||||
proxy.collectOOMReportArtifacts(reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
public func triggerGoCrash() throws {
|
||||
try performXPCCallVoid("triggerGoCrash") { proxy, reply in
|
||||
proxy.triggerGoCrash(reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
public func triggerNativeCrash() throws {
|
||||
try performXPCCallVoid("triggerNativeCrash") { proxy, reply in
|
||||
proxy.triggerNativeCrash(reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
public func getVersion() throws -> String {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: String?
|
||||
var resultError: NSError?
|
||||
|
||||
let conn = getConnection()
|
||||
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ error in
|
||||
logger.error("getVersion XPC error: \(error.localizedDescription)")
|
||||
resultError = error as NSError
|
||||
semaphore.signal()
|
||||
}) as? RootHelperProtocol else {
|
||||
connectionLock.lock()
|
||||
connection = nil
|
||||
connectionLock.unlock()
|
||||
conn.invalidate()
|
||||
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to get RootHelper proxy",
|
||||
])
|
||||
try performXPCCall("getVersion") { proxy, reply in
|
||||
proxy.getVersion { version in
|
||||
reply(version as String?, nil)
|
||||
}
|
||||
}
|
||||
|
||||
proxy.getVersion { version in
|
||||
result = version
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
let timeout = DispatchTime.now() + .seconds(5)
|
||||
if semaphore.wait(timeout: timeout) == .timedOut {
|
||||
let error = NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "getVersion request timeout",
|
||||
])
|
||||
logger.error("getVersion: timeout")
|
||||
throw error
|
||||
}
|
||||
|
||||
if let error = resultError {
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let value = result else {
|
||||
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "getVersion returned nil",
|
||||
])
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user