From 88ee9d5097276021cc257db17785ea58399080d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 5 Oct 2024 10:02:48 +0800 Subject: [PATCH] Add FDA request dialog --- .../Views/Dashboard/DashboardView.swift | 57 +++++++++++++++++++ .../Views/Setting/ProfileOverrideView.swift | 3 +- Library/Network/ExtensionErrors.swift | 5 ++ Library/Network/ExtensionProfile.swift | 5 ++ Library/Network/ExtensionProvider.swift | 1 + SystemExtension/PacketTunnelProvider.swift | 15 +++++ sing-box.xcodeproj/project.pbxproj | 15 +++-- 7 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 Library/Network/ExtensionErrors.swift diff --git a/ApplicationLibrary/Views/Dashboard/DashboardView.swift b/ApplicationLibrary/Views/Dashboard/DashboardView.swift index 5c4423d..6221a60 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardView.swift @@ -86,6 +86,7 @@ public struct DashboardView: View { @EnvironmentObject private var environments: ExtensionEnvironments @EnvironmentObject private var profile: ExtensionProfile @State private var alert: Alert? + @State private var notStarted = false var body: some View { VStack { @@ -93,10 +94,23 @@ public struct DashboardView: View { } .alertBinding($alert) .onChangeCompat(of: profile.status) { newValue in + if newValue == .connected { + notStarted = false + } if newValue == .disconnecting || newValue == .connected { Task { await checkServiceError() } + } else if newValue == .connecting { + notStarted = true + } else if newValue == .disconnected { + if #available(iOS 16.0, macOS 13.0, tvOS 17.0, *) { + if notStarted { + Task { + await checkLastDisconnectError() + } + } + } } } } @@ -111,5 +125,48 @@ public struct DashboardView: View { alert = Alert(title: Text("Service Error"), message: Text(message)) } } + + @available(iOS 16.0, macOS 13.0, tvOS 17.0, *) + private nonisolated func checkLastDisconnectError() async { + var myError: NSError + do { + try await profile.fetchLastDisconnectError() + return + } catch { + myError = error as NSError + } + #if os(macOS) + if myError.domain == "Library.FullDiskAccessPermissionRequired" { + await MainActor.run { + alert = Alert( + title: Text("Full Disk Access permission is required"), + message: Text("Please grant the permission for SFMExtension, then we can continue."), + primaryButton: .default(Text("Authorize"), action: openFDASettings), + secondaryButton: .cancel() + ) + } + return + } + #endif + let message = myError.localizedDescription + await MainActor.run { + alert = Alert(title: Text("Service Error"), message: Text(message)) + } + } + + #if os(macOS) + + private func openFDASettings() { + if NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!) { + return + } + if #available(macOS 13, *) { + NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/System Settings.app")) + } else { + NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Applications/System Preferences.app")) + } + } + + #endif } } diff --git a/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift index 060baed..5f76352 100644 --- a/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift +++ b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift @@ -23,7 +23,8 @@ public struct ProfileOverrideView: View { } FormToggle("No Default Route", """ - By default, segment routing is used in `auto_route` instead of global routing. If `*_` exists in the configuration, this item will not take effect on the corresponding network (commonly used to resolve HomeKit compatibility issues). + By default, segment routing is used in `auto_route` instead of global routing. + If `*_` exists in the configuration, this item will not take effect on the corresponding network (commonly used to resolve HomeKit compatibility issues). """, $autoRouteUseSubRangesByDefault) { newValue in await SharedPreferences.autoRouteUseSubRangesByDefault.set(newValue) } diff --git a/Library/Network/ExtensionErrors.swift b/Library/Network/ExtensionErrors.swift new file mode 100644 index 0000000..6301451 --- /dev/null +++ b/Library/Network/ExtensionErrors.swift @@ -0,0 +1,5 @@ +import Foundation + +public enum FullDiskAccessPermissionRequired: Error { + case error +} diff --git a/Library/Network/ExtensionProfile.swift b/Library/Network/ExtensionProfile.swift index c738111..d68edc1 100644 --- a/Library/Network/ExtensionProfile.swift +++ b/Library/Network/ExtensionProfile.swift @@ -49,6 +49,11 @@ public class ExtensionProfile: ObservableObject { try await manager.saveToPreferences() } + @available(iOS 16.0, macOS 13.0, tvOS 17.0, *) + public func fetchLastDisconnectError() async throws { + try await connection.fetchLastDisconnectError() + } + public func start() async throws { await fetchProfile() manager.isEnabled = true diff --git a/Library/Network/ExtensionProvider.swift b/Library/Network/ExtensionProvider.swift index 54199dd..c414bc5 100644 --- a/Library/Network/ExtensionProvider.swift +++ b/Library/Network/ExtensionProvider.swift @@ -32,6 +32,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { LibboxRedirectStderr(FilePath.cacheDirectory.appendingPathComponent("stderr.log").relativePath, &error) if let error { writeFatalError("(packet-tunnel) redirect stderr error: \(error.localizedDescription)") + return } await LibboxSetMemoryLimit(!SharedPreferences.ignoreMemoryLimit.get()) diff --git a/SystemExtension/PacketTunnelProvider.swift b/SystemExtension/PacketTunnelProvider.swift index 4db3009..dcd7683 100644 --- a/SystemExtension/PacketTunnelProvider.swift +++ b/SystemExtension/PacketTunnelProvider.swift @@ -1,5 +1,7 @@ +import Libbox import Library import NetworkExtension +import System class PacketTunnelProvider: ExtensionProvider { override func startTunnel(options: [String: NSObject]?) async throws { @@ -10,6 +12,19 @@ class PacketTunnelProvider: ExtensionProvider { let username = usernameObject as! NSString FilePath.sharedDirectory = URL(filePath: "/Users/\(username)/Library/Group Containers/\(FilePath.groupName)") FilePath.iCloudDirectory = URL(filePath: "/Users/\(username)/Library/Mobile Documents/iCloud~\(FilePath.packageName.replacingOccurrences(of: ".", with: "~"))").appendingPathComponent("Documents", isDirectory: true) + let databasePath = FilePath.sharedDirectory.appendingPathComponent("settings.db").relativePath + if !FileManager.default.isReadableFile(atPath: databasePath) { + do { + let fd = try FileDescriptor.open(databasePath, .readOnly) + try! fd.close() + NSLog("Can access \(databasePath)") + } catch { + NSLog("Can't access \(databasePath): \(error.localizedDescription)") + } + try await Task.sleep(nanoseconds: NSEC_PER_MSEC * 100) + throw FullDiskAccessPermissionRequired.error + } + self.username = String(username) try await super.startTunnel(options: options) } diff --git a/sing-box.xcodeproj/project.pbxproj b/sing-box.xcodeproj/project.pbxproj index abe916a..cb826d0 100644 --- a/sing-box.xcodeproj/project.pbxproj +++ b/sing-box.xcodeproj/project.pbxproj @@ -117,6 +117,7 @@ 3AE4D0BD2A6E2DDC009FEA9E /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; }; 3AE4D0BE2A6E2DDC009FEA9E /* Library.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC211D2A459B4700A63465 /* Library.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 3AE4D0C12A6E4852009FEA9E /* InstallSystemExtensionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE4D0C02A6E4852009FEA9E /* InstallSystemExtensionButton.swift */; }; + 3AE5A6C32C9FD96A002AA4FE /* ExtensionErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE5A6C22C9FD967002AA4FE /* ExtensionErrors.swift */; }; 3AEAEE992A4F16430059612D /* Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3A096F862A4ED3DE00D4A2ED /* Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 3AEC20F62A459AB400A63465 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AEC20F52A459AB400A63465 /* Application.swift */; }; 3AEC20FA2A459AB500A63465 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AEC20F92A459AB500A63465 /* Assets.xcassets */; }; @@ -456,6 +457,7 @@ 3A27D9012A89C6870031EBCC /* ExtensionEnvironments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionEnvironments.swift; sourceTree = ""; }; 3A2EAEEC2A6F4CBB00D00DE3 /* IndependentApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndependentApplicationDelegate.swift; sourceTree = ""; }; 3A2F29EA2C998A5D007E024C /* Export.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Export.plist; sourceTree = ""; }; + 3A334ECF2C0F621E00E9C577 /* ConnectionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionDetailsView.swift; sourceTree = ""; }; 3A3AB2A62B70C146001815AE /* CoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreView.swift; sourceTree = ""; }; 3A3AB2A82B70C5F1001815AE /* RequestReviewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestReviewButton.swift; sourceTree = ""; }; 3A3DEBE12A4FFA1A00373BF4 /* ExtensionFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExtensionFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/ExtensionFoundation.framework; sourceTree = DEVELOPER_DIR; }; @@ -529,6 +531,7 @@ 3AE1719F2A8128DD00393060 /* TVExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TVExtension.entitlements; sourceTree = ""; }; 3AE4D0B62A6E2C01009FEA9E /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; 3AE4D0C02A6E4852009FEA9E /* InstallSystemExtensionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallSystemExtensionButton.swift; sourceTree = ""; }; + 3AE5A6C22C9FD967002AA4FE /* ExtensionErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionErrors.swift; sourceTree = ""; }; 3AEAEE9C2A4F1A9D0059612D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3AEC20DB2A4599D000A63465 /* Libbox.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Libbox.xcframework; sourceTree = ""; }; 3AEC20F32A459AB400A63465 /* sing-box.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sing-box.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -914,6 +917,7 @@ 3AEC21432A45A92B00A63465 /* Network */ = { isa = PBXGroup; children = ( + 3AE5A6C22C9FD967002AA4FE /* ExtensionErrors.swift */, 3A096F892A4ED3DE00D4A2ED /* ExtensionProvider.swift */, 3AF342AA2A4AA173002B34AC /* Extension+Iterator.swift */, 3AF342A82A4AA155002B34AC /* Extension+RunBlocking.swift */, @@ -1583,6 +1587,7 @@ 3AEC21452A45A93800A63465 /* HTTPClient.swift in Sources */, 3AEC213C2A459FDF00A63465 /* Databse.swift in Sources */, 3A60CC292B70A7C400D2D682 /* Color+Extension.swift in Sources */, + 3AE5A6C32C9FD96A002AA4FE /* ExtensionErrors.swift in Sources */, 3A4EAD3C2A4FECCE005435B3 /* NEVPNStatus+isConnected.swift in Sources */, 3ADBB4252A7389640041D44F /* ProfileServer.swift in Sources */, 3AEC21422A45A8FF00A63465 /* Profile+Update.swift in Sources */, @@ -1837,7 +1842,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1880,7 +1884,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2439,7 +2442,6 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; - ENABLE_MODULE_VERIFIER = YES; ENABLE_ON_DEMAND_RESOURCES = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; @@ -2487,7 +2489,6 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; - ENABLE_MODULE_VERIFIER = YES; ENABLE_ON_DEMAND_RESOURCES = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; @@ -2532,7 +2533,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SystemExtension/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = SystemExtension; + INFOPLIST_KEY_CFBundleDisplayName = SFMExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSystemExtensionUsageDescription = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -2568,7 +2569,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SystemExtension/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = SystemExtension; + INFOPLIST_KEY_CFBundleDisplayName = SFMExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSystemExtensionUsageDescription = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -2685,7 +2686,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; @@ -2725,7 +2725,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "";