From a4a4780e0296889cbfa44bb578149df5416fc8c7 Mon Sep 17 00:00:00 2001 From: hiddify <114227601+hiddify-com@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:17:13 +0330 Subject: [PATCH] update ios --- .../PacketTunnelProvider.swift | 3 +- .../SingBox/ExtensionPlatformInterface.swift | 301 ++++++++++++++---- .../SingBox/ExtensionProvider.swift | 64 ++-- ios/Podfile.lock | 8 +- .../xcschemes/HiddifyPacketTunnel.xcscheme | 10 +- ios/Runner/AppDelegate.swift | 8 +- ios/Runner/Handlers/MethodHandler.swift | 19 +- ios/Shared/CommandClient.swift | 224 ++++++------- lib/hiddifycore/hiddify_core_service.dart | 1 + 9 files changed, 418 insertions(+), 220 deletions(-) diff --git a/ios/HiddifyPacketTunnel/PacketTunnelProvider.swift b/ios/HiddifyPacketTunnel/PacketTunnelProvider.swift index 6938ccb4..73cd6bff 100644 --- a/ios/HiddifyPacketTunnel/PacketTunnelProvider.swift +++ b/ios/HiddifyPacketTunnel/PacketTunnelProvider.swift @@ -29,8 +29,9 @@ class PacketTunnelProvider: ExtensionProvider { } override func handleAppMessage(_ messageData: Data) async -> Data? { - NSLog("H?C2") + let message = String(data: messageData, encoding: .utf8) +// NSLog("H?C2"+message??"") switch message { case "stats": return "\(upload),\(download)".data(using: .utf8)! diff --git a/ios/HiddifyPacketTunnel/SingBox/ExtensionPlatformInterface.swift b/ios/HiddifyPacketTunnel/SingBox/ExtensionPlatformInterface.swift index 7cee7694..3faa275e 100644 --- a/ios/HiddifyPacketTunnel/SingBox/ExtensionPlatformInterface.swift +++ b/ios/HiddifyPacketTunnel/SingBox/ExtensionPlatformInterface.swift @@ -27,17 +27,20 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc } private func openTun0(_ options: LibboxTunOptionsProtocol?, _ ret0_: UnsafeMutablePointer?) async throws { - NSLog("H?A2") guard let options else { - throw NSError(domain: "nil options", code: 0) + throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Nil options")]) } guard let ret0_ else { - throw NSError(domain: "nil return pointer", code: 0) + throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Nil return pointer")]) } - let autoRouteUseSubRangesByDefault = true// await SharedPreferences.autoRouteUseSubRangesByDefault.get() - let excludeAPNs = false //await SharedPreferences.excludeAPNsRoute.get() +// let prefs = tunnel.overridePreferences ?? ExtensionProvider.OverridePreferences() + let autoRouteUseSubRangesByDefault = true//prefs.autoRouteUseSubRangesByDefault // await SharedPreferences.autoRouteUseSubRangesByDefault.get() + let excludeAPNs = false//prefs.excludeAPNsRoute //await SharedPreferences.excludeAPNsRoute.get() + let excludeDefaultRoute = false// prefs.excludeDefaultRoute + let systemProxyEnabled = false//prefs.systemProxyEnabled + let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") if options.getAutoRoute() { settings.mtu = NSNumber(value: options.getMTU()) @@ -84,13 +87,13 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let ipv4RoutePrefix = inet4RouteExcludeAddressIterator.next()! ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: ipv4RoutePrefix.address(), subnetMask: ipv4RoutePrefix.mask())) } -// if await SharedPreferences.excludeDefaultRoute.get(), !ipv4Routes.isEmpty { -// if !ipv4ExcludeRoutes.contains(where: { it in -// it.destinationAddress == "0.0.0.0" && it.destinationSubnetMask == "255.255.255.254" -// }) { -// ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "255.255.255.254")) -// } -// } + if excludeDefaultRoute, !ipv4Routes.isEmpty { + if !ipv4ExcludeRoutes.contains(where: { it in + it.destinationAddress == "0.0.0.0" && it.destinationSubnetMask == "255.255.255.254" + }) { + ipv4ExcludeRoutes.append(NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "255.255.255.254")) + } + } if excludeAPNs, !ipv4Routes.isEmpty { if !ipv4ExcludeRoutes.contains(where: { it in it.destinationAddress == "17.0.0.0" && it.destinationSubnetMask == "255.0.0.0" @@ -138,7 +141,13 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let ipv6RoutePrefix = inet6RouteExcludeAddressIterator.next()! ipv6ExcludeRoutes.append(NEIPv6Route(destinationAddress: ipv6RoutePrefix.address(), networkPrefixLength: NSNumber(value: ipv6RoutePrefix.prefix()))) } - + if excludeDefaultRoute, !ipv6Routes.isEmpty { + if !ipv6ExcludeRoutes.contains(where: { it in + it.destinationAddress == "::" && it.destinationNetworkPrefixLength == 127 + }) { + ipv6ExcludeRoutes.append(NEIPv6Route(destinationAddress: "::", networkPrefixLength: 127)) + } + } ipv6Settings.includedRoutes = ipv6Routes ipv6Settings.excludedRoutes = ipv6ExcludeRoutes settings.ipv6Settings = ipv6Settings @@ -149,10 +158,10 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc let proxyServer = NEProxyServer(address: options.getHTTPProxyServer(), port: Int(options.getHTTPProxyServerPort())) proxySettings.httpServer = proxyServer proxySettings.httpsServer = proxyServer -// if await SharedPreferences.systemProxyEnabled.get() { -// proxySettings.httpEnabled = true -// proxySettings.httpsEnabled = true -// } + if systemProxyEnabled { + proxySettings.httpEnabled = true + proxySettings.httpsEnabled = true + } var bypassDomains: [String] = [] let bypassDomainIterator = options.getHTTPProxyBypassDomain()! @@ -203,80 +212,212 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc } public func usePlatformAutoDetectControl() -> Bool { - true + false } - - public func autoDetectControl(_: Int32) throws {} - - public func findConnectionOwner(_: Int32, sourceAddress _: String?, sourcePort _: Int32, destinationAddress _: String?, destinationPort _: Int32, ret0_ _: UnsafeMutablePointer?) throws { - throw NSError(domain: "not implemented", code: 0) - } - - public func packageName(byUid _: Int32, error _: NSErrorPointer) -> String { - "" - } - - public func uid(byPackageName _: String?, ret0_ _: UnsafeMutablePointer?) throws { - throw NSError(domain: "not implemented", code: 0) + public func findConnectionOwner(_ ipProtocol: Int32, sourceAddress: String?, sourcePort: Int32, destinationAddress: String?, destinationPort: Int32) throws -> LibboxConnectionOwner { + #if os(macOS) + if Variant.useSystemExtension { + guard let sourceAddress, let destinationAddress else { + throw NSError(domain: "findConnectionOwner", code: 0, userInfo: [ + NSLocalizedDescriptionKey: "Missing source or destination address", + ]) + } + let owner = try RootHelperClient.shared.findConnectionOwner( + ipProtocol: ipProtocol, + sourceAddress: sourceAddress, + sourcePort: sourcePort, + destinationAddress: destinationAddress, + destinationPort: destinationPort + ) + let result = LibboxConnectionOwner() + result.userId = owner.userId + result.userName = owner.userName + result.processPath = owner.processPath + return result + } + #endif + throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Not implemented")]) } public func useProcFS() -> Bool { false } - func writeLogs(_ messageList: (any LibboxStringIteratorProtocol)?) { -// guard let message else { -// return -// } -// tunnel.writeMessage(message) + public func writeLog(_ message: String?) { + guard let message else { + return + } + tunnel.writeMessage(message) } - public func usePlatformDefaultInterfaceMonitor() -> Bool { - false + private var nwMonitor: NWPathMonitor? + + public func startDefaultInterfaceMonitor(_ listener: LibboxInterfaceUpdateListenerProtocol?) throws { + return + guard let listener else { + return + } + let monitor = NWPathMonitor() + nwMonitor = monitor + let semaphore = DispatchSemaphore(value: 0) + monitor.pathUpdateHandler = { path in + self.onUpdateDefaultInterface(listener, path) + semaphore.signal() + monitor.pathUpdateHandler = { path in + self.onUpdateDefaultInterface(listener, path) + } + } + monitor.start(queue: DispatchQueue.global()) + semaphore.wait() } - public func startDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {} + private func onUpdateDefaultInterface(_ listener: LibboxInterfaceUpdateListenerProtocol, _ path: Network.NWPath) { + return + guard path.status != .unsatisfied, + let defaultInterface = path.availableInterfaces.first + else { + listener.updateDefaultInterface("", interfaceIndex: -1, isExpensive: false, isConstrained: false) + return + } + listener.updateDefaultInterface(defaultInterface.name, interfaceIndex: Int32(defaultInterface.index), isExpensive: path.isExpensive, isConstrained: path.isConstrained) + } - public func closeDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws {} - - public func useGetter() -> Bool { - false + public func closeDefaultInterfaceMonitor(_: LibboxInterfaceUpdateListenerProtocol?) throws { + nwMonitor?.cancel() + nwMonitor = nil } public func getInterfaces() throws -> LibboxNetworkInterfaceIteratorProtocol { throw NSError(domain: "not implemented", code: 0) + guard let nwMonitor else { + throw NSError(domain: "ExtensionPlatformInterface", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "NWMonitor not started")]) + } + let path = nwMonitor.currentPath + if path.status == .unsatisfied { + return networkInterfaceArray([]) + } + var interfaces: [LibboxNetworkInterface] = [] + for it in path.availableInterfaces { + let interface = LibboxNetworkInterface() + interface.name = it.name + interface.index = Int32(it.index) + switch it.type { + case .wifi: + interface.type = LibboxInterfaceTypeWIFI + case .cellular: + interface.type = LibboxInterfaceTypeCellular + case .wiredEthernet: + interface.type = LibboxInterfaceTypeEthernet + default: + interface.type = LibboxInterfaceTypeOther + } + interfaces.append(interface) + } + return networkInterfaceArray(interfaces) + } + + class networkInterfaceArray: NSObject, LibboxNetworkInterfaceIteratorProtocol { + private var iterator: IndexingIterator<[LibboxNetworkInterface]> + init(_ array: [LibboxNetworkInterface]) { + iterator = array.makeIterator() + } + + private var nextValue: LibboxNetworkInterface? + + func hasNext() -> Bool { + nextValue = iterator.next() + return nextValue != nil + } + + func next() -> LibboxNetworkInterface? { + nextValue + } } public func underNetworkExtension() -> Bool { true } + public func includeAllNetworks() -> Bool { - #if !os(tvOS) - // return SharedPreferences.includeAllNetworks.getBlocking() + #if os(tvOS) return false #else return false +// return tunnel.overridePreferences?.includeAllNetworks ?? false #endif } + public func clearDNSCache() { guard let networkSettings else { return } - tunnel.reasserting = true - tunnel.setTunnelNetworkSettings(nil) { _ in + runBlocking { + self.tunnel.reasserting = true + defer { self.tunnel.reasserting = false } + await withCheckedContinuation { continuation in + self.tunnel.setTunnelNetworkSettings(nil) { _ in + continuation.resume() + } + } + await withCheckedContinuation { continuation in + self.tunnel.setTunnelNetworkSettings(networkSettings) { _ in + continuation.resume() + } + } } - tunnel.setTunnelNetworkSettings(networkSettings) { _ in - } - tunnel.reasserting = false } + public func readWIFIState() -> LibboxWIFIState? { +// #if os(iOS) +// let network = runBlocking { +// await NEHotspotNetwork.fetchCurrent() +// } +// guard let network else { +// return nil +// } +// return LibboxWIFIState(network.ssid, wifiBSSID: network.bssid)! +// #elseif os(macOS) +// if Variant.useSystemExtension { +// return UserServiceClient.shared.readWIFIState() +// } +// guard let interface = CWWiFiClient.shared().interface() else { +// return nil +// } +// guard let ssid = interface.ssid() else { +// return nil +// } +// guard let bssid = interface.bssid() else { +// return nil +// } +// return LibboxWIFIState(ssid, wifiBSSID: bssid)! +// #else + return nil +// #endif + } + + public func readWIFISSID() -> String? { +// #if os(iOS) +// return runBlocking { +// await NEHotspotNetwork.fetchCurrent()?.ssid +// } +// #elseif os(macOS) +// return CWWiFiClient.shared().interface()?.ssid() +// #else + return nil +// #endif + } + +// public func serviceStop() throws { +// tunnel.stopService() +// } + public func serviceReload() throws { - runBlocking { [self] in - await tunnel.reloadService() + try runBlocking { [self] in + try await tunnel.reloadService() } } - public func getSystemProxyStatus() -> LibboxSystemProxyStatus? { + public func getSystemProxyStatus() throws -> LibboxSystemProxyStatus { let status = LibboxSystemProxyStatus() guard let networkSettings else { return status @@ -313,24 +454,56 @@ public class ExtensionPlatformInterface: NSObject, LibboxPlatformInterfaceProtoc } } - public func postServiceClose() { - NSLog("H?A3") - // TODO + public func writeDebugMessage(_ message: String?) { + guard let message else { + return + } +// tunnel.writeMessage(message) } func reset() { - NSLog("H?A4") networkSettings = nil + nwMonitor?.cancel() + nwMonitor = nil } - - public func writeLog(_ message: String?) { - } - public func send(_ notification: LibboxNotification?) throws { + #if !os(tvOS) + guard let notification else { + return + } + #if os(macOS) + if Variant.useSystemExtension { + try UserServiceClient.shared.sendNotification(notification) + return + } + #endif +// let center = UNUserNotificationCenter.current() +// let content = UNMutableNotificationContent() +// +// content.title = notification.title +// content.subtitle = notification.subtitle +// content.body = notification.body +// if !notification.openURL.isEmpty { +// content.userInfo["OPEN_URL"] = notification.openURL +// content.categoryIdentifier = "OPEN_URL" +// } +// content.interruptionLevel = .active +// let request = UNNotificationRequest(identifier: notification.identifier, content: content, trigger: nil) +// try runBlocking { +// try await center.requestAuthorization(options: [.alert]) +// try await center.add(request) +// } + #endif } - - public func readWIFIState() -> LibboxWIFIState? { - return nil; + + public func localDNSTransport() -> (any LibboxLocalDNSTransportProtocol)? { + nil } + + public func systemCertificates() -> (any LibboxStringIteratorProtocol)? { + nil + } + public func autoDetectControl(_: Int32) throws {} + } diff --git a/ios/HiddifyPacketTunnel/SingBox/ExtensionProvider.swift b/ios/HiddifyPacketTunnel/SingBox/ExtensionProvider.swift index 12182ba9..50eed0f0 100644 --- a/ios/HiddifyPacketTunnel/SingBox/ExtensionProvider.swift +++ b/ios/HiddifyPacketTunnel/SingBox/ExtensionProvider.swift @@ -7,7 +7,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { public static let errorFile = FilePath.workingDirectory.appendingPathComponent("network_extension_error.log") private let logger = Logger(subsystem: "apple.hiddify.com.HiddifyPacketTunnel", category: "PacketTunnel") - private var commandServer: LibboxCommandServer! +// private var commandServer: LibboxCommandServer! private var systemProxyAvailable = false private var systemProxyEnabled = false private var platformInterface: ExtensionPlatformInterface! @@ -54,14 +54,17 @@ open class ExtensionProvider: NEPacketTunnelProvider { } // Initialize mobile setup with error handling var setupError: NSError? - MobileSetup( - sharedDir, - workDir, - cacheDir, - 4, - "127.0.0.1:\(grpcServiceModePort)", - "", - false, + let opts = MobileSetupOptions() + opts.basePath = sharedDir + opts.workingDir = workDir + opts.tempDir = cacheDir + opts.listen = "127.0.0.1:\(grpcServiceModePort)" + opts.secret = "" + opts.debug = false + opts.mode = 4 + opts.fixAndroidStack = false + + MobileSetup(opts, platformInterface, &setupError ) @@ -70,6 +73,7 @@ open class ExtensionProvider: NEPacketTunnelProvider { throw setupError } + LibboxSetMemoryLimit(!disableMemoryLimit) writeMessage("(packet-tunnel) setup completed successfully") @@ -84,19 +88,19 @@ open class ExtensionProvider: NEPacketTunnelProvider { } private func startService(_ config: String) async throws { - writeMessage("Starting service") - var error: NSError? - - do { - try MobileStart("", config, &error) - if let error = error { - throw error - } - writeMessage("(packet-tunnel) service started successfully") - } catch { - writeFatalError("(packet-tunnel) error: start service: \(error.localizedDescription)") - throw error - } +// writeMessage("Starting service") +// var error: NSError? +// +// do { +//// try MobileStart("", "", &error) +// if let error = error { +// throw error +// } +// writeMessage("(packet-tunnel) service started successfully") +// } catch { +// writeFatalError("(packet-tunnel) error: start service: \(error.localizedDescription)") +// throw error +// } } private func createRequiredDirectories() throws { @@ -155,13 +159,13 @@ open class ExtensionProvider: NEPacketTunnelProvider { writeMessage("(packet-tunnel) stopping, reason: \(reason)") stopService() - // Allow time for cleanup - try? await Task.sleep(nanoseconds: 100 * NSEC_PER_MSEC) - - if let server = commandServer { - try? server.close() - commandServer = nil - } +// // Allow time for cleanup +// try? await Task.sleep(nanoseconds: 100 * NSEC_PER_MSEC) +// +// if let server = commandServer { +// try? server.close() +// commandServer = nil +// } } private func stopService() { @@ -197,11 +201,13 @@ open class ExtensionProvider: NEPacketTunnelProvider { override open func sleep() async { logger.debug("Entering sleep mode") + MobilePause() // Add any sleep mode handling if needed } override open func wake() { logger.debug("Waking from sleep") + MobileWake() // Add any wake handling if needed } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 989be98c..746116da 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -110,9 +110,9 @@ PODS: - pointer_interceptor_ios (0.0.1): - Flutter - PromisesObjC (2.4.0) - - SDWebImage (5.21.3): - - SDWebImage/Core (= 5.21.3) - - SDWebImage/Core (5.21.3) + - SDWebImage (5.21.5): + - SDWebImage/Core (= 5.21.5) + - SDWebImage/Core (5.21.5) - Sentry/HybridSDK (8.46.0) - sentry_flutter (8.14.0): - Flutter @@ -262,7 +262,7 @@ SPEC CHECKSUMS: path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 pointer_interceptor_ios: 508241697ff0947f853c061945a8b822463947c1 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a + SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 sentry_flutter: 187f9b6b06f00f36b4930ec7ea9f34c254095d15 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/HiddifyPacketTunnel.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/HiddifyPacketTunnel.xcscheme index a94f462d..71110b84 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/HiddifyPacketTunnel.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/HiddifyPacketTunnel.xcscheme @@ -56,8 +56,12 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" launchAutomaticallySubstyle = "2"> - + + + - + commandClient.logMaxLines { +//// commandClient.logList.removeFirst() +//// } +//// commandClient.logList.append(message) +//// } +// } +// +// func writeStatus(_ message: LibboxStatusMessage?) { +// DispatchQueue.main.async { [self] in +// commandClient.status = message +// } +// } +// +// func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { +// guard let groups else { // return // } -// DispatchQueue.main.async { [self] in -// if commandClient.logList.count > commandClient.logMaxLines { -// commandClient.logList.removeFirst() +// var sbGroups = [SBGroup]() +// while groups.hasNext() { +// let group = groups.next()! +// var items = [SBItem]() +// let groupItems = group.getItems() +// while groupItems?.hasNext() ?? false { +// let item = groupItems?.next()! +// items.append(SBItem(tag: item!.tag, +// type: item!.type, +// urlTestDelay: Int(item!.urlTestDelay) +// ) +// ) // } -// commandClient.logList.append(message) +// +// sbGroups.append(.init(tag: group.tag, +// type: group.type, +// selected: group.selected, +// items: items) +// ) +// // } - } - - func writeStatus(_ message: LibboxStatusMessage?) { - DispatchQueue.main.async { [self] in - commandClient.status = message - } - } - - func writeGroups(_ groups: LibboxOutboundGroupIteratorProtocol?) { - guard let groups else { - return - } - var sbGroups = [SBGroup]() - while groups.hasNext() { - let group = groups.next()! - var items = [SBItem]() - let groupItems = group.getItems() - while groupItems?.hasNext() ?? false { - let item = groupItems?.next()! - items.append(SBItem(tag: item!.tag, - type: item!.type, - urlTestDelay: Int(item!.urlTestDelay) - ) - ) - } - - sbGroups.append(.init(tag: group.tag, - type: group.type, - selected: group.selected, - items: items) - ) - - } - DispatchQueue.main.async { [self] in - commandClient.groups = sbGroups - } - } - - func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { - } - - func updateClashMode(_ newMode: String?) { - } - func write(_ message: LibboxConnections?) { - } - - } +// DispatchQueue.main.async { [self] in +// commandClient.groups = sbGroups +// } +// } +// +// func initializeClashMode(_ modeList: LibboxStringIteratorProtocol?, currentMode: String?) { +// } +// +// func updateClashMode(_ newMode: String?) { +// } +// func write(_ message: LibboxConnections?) { +// } +// +// } } diff --git a/lib/hiddifycore/hiddify_core_service.dart b/lib/hiddifycore/hiddify_core_service.dart index 454c36c2..d464d6fc 100644 --- a/lib/hiddifycore/hiddify_core_service.dart +++ b/lib/hiddifycore/hiddify_core_service.dart @@ -42,6 +42,7 @@ class HiddifyCoreService with InfraLogger { setup(dirs, debug) .mapLeft((e) { loggy.error(e); + if (PlatformUtils.isIOS) return; ref.read(inAppNotificationControllerProvider).showErrorToast(e); }) .map((_) {