From c4d3421373f308a629dab67196cefa0bf2f5da2d Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Sun, 15 Feb 2026 01:25:05 +0800 Subject: [PATCH] ios proxy pass domains (#527) --- ios/ProxyPin/PacketTunnelProvider.swift | 15 ++++++++++++++- ios/Runner/AppDelegate.swift | 4 ++-- ios/Runner/VpnManager.swift | 14 ++++++++++---- lib/network/util/system_proxy.dart | 3 +++ lib/ui/mobile/menu/bottom_navigation.dart | 13 +++++++++---- lib/ui/mobile/menu/drawer.dart | 4 ++++ 6 files changed, 42 insertions(+), 11 deletions(-) diff --git a/ios/ProxyPin/PacketTunnelProvider.swift b/ios/ProxyPin/PacketTunnelProvider.swift index 2b8d8f5..425e5f3 100644 --- a/ios/ProxyPin/PacketTunnelProvider.swift +++ b/ios/ProxyPin/PacketTunnelProvider.swift @@ -24,6 +24,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let proxyPort = conf["proxyPort"] as! Int let ipProxy = conf["ipProxy"] as! Bool? ?? false + // parse proxyPassDomains: accept either [String] or comma-separated String + var proxyPassDomains: [String]? = nil + if let arr = conf["proxyPassDomains"] as? [String] { + proxyPassDomains = arr.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } + } else if let csv = conf["proxyPassDomains"] as? String { + let list = csv.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } + proxyPassDomains = list.isEmpty ? nil : list + } + // let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: host) NSLog(conf.debugDescription) @@ -57,8 +66,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { proxySettings.httpServer = NEProxyServer(address: host, port: proxyPort) proxySettings.httpsEnabled = true proxySettings.httpsServer = NEProxyServer(address: host, port: proxyPort) + // If a proxyPassDomains list was provided, use it as the exceptionList so these domains bypass the proxy. + if let pass = proxyPassDomains { + proxySettings.exceptionList = pass + } + proxySettings.matchDomains = [""] - networkSettings.proxySettings = proxySettings networkSettings.ipv4Settings = ipv4Settings diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index f321236..3c2053f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -21,10 +21,10 @@ import NetworkExtension } else if ("restartVpn" == call.method){ let arguments = call.arguments as? Dictionary // VpnManager.shared.disconnect() - VpnManager.shared.restartConnect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool) + VpnManager.shared.restartConnect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool, proxyPassDomains: arguments?["proxyPassDomains"] as? [String]) } else { let arguments = call.arguments as? Dictionary - VpnManager.shared.connect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool) + VpnManager.shared.connect(host: arguments?["proxyHost"] as? String ,port: arguments?["proxyPort"] as? Int, ipProxy: arguments?["ipProxy"] as? Bool, proxyPassDomains: arguments?["proxyPassDomains"] as? [String]) } }) diff --git a/ios/Runner/VpnManager.swift b/ios/Runner/VpnManager.swift index efed9fe..beacd50 100755 --- a/ios/Runner/VpnManager.swift +++ b/ios/Runner/VpnManager.swift @@ -17,6 +17,7 @@ class VpnManager{ public var proxyHost: String = "127.0.0.1" public var proxyPort: Int = 9099 public var ipProxy: Bool = false + public var proxyPassDomains: [String]? static let shared = VpnManager() var observerAdded: Bool = false @@ -103,7 +104,11 @@ extension VpnManager{ conf["proxyHost"] = self.proxyHost as AnyObject conf["proxyPort"] = self.proxyPort as AnyObject conf["ipProxy"] = self.ipProxy as AnyObject - + // Bridge Swift [String] to NSArray (Objective-C) before inserting into AnyObject dictionary + if let passDomains = self.proxyPassDomains { + conf["proxyPassDomains"] = passDomains as NSArray + } + let orignConf = manager.protocolConfiguration as! NETunnelProviderProtocol orignConf.providerConfiguration = conf @@ -147,10 +152,11 @@ extension VpnManager{ // Actions extension VpnManager{ - func connect(host: String?, port: Int?, ipProxy: Bool? = false) { + func connect(host: String?, port: Int?, ipProxy: Bool? = false, proxyPassDomains: [String]? = nil) { self.proxyHost = host ?? self.proxyHost self.proxyPort = port ?? self.proxyPort self.ipProxy = ipProxy ?? false + self.proxyPassDomains = proxyPassDomains ?? self.proxyPassDomains self.loadAndCreatePrividerManager { (manager) in guard let manager = manager else{return} @@ -163,7 +169,7 @@ extension VpnManager{ } } - func restartConnect(host: String?, port: Int?, ipProxy: Bool? = false) { + func restartConnect(host: String?, port: Int?, ipProxy: Bool? = false, proxyPassDomains: [String]? = nil) { self.proxyHost = host ?? self.proxyHost self.proxyPort = port ?? self.proxyPort self.ipProxy = ipProxy ?? false @@ -173,7 +179,7 @@ extension VpnManager{ activeVPN = nil } - self.connect(host: host, port: port, ipProxy: ipProxy) + self.connect(host: host, port: port, ipProxy: ipProxy, proxyPassDomains: proxyPassDomains) } func disconnect() { diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index e4d0452..382bc4a 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -55,6 +55,9 @@ class SystemProxy { if (Platform.isAndroid) { return '192.168.0.0/16;10.0.0.0/8;172.16.0.0/12;127.0.0.1;localhost'; } + if (Platform.isIOS) { + return '192.168.0.0/16;10.0.0.0/8;172.16.0.0/12;127.0.0.1;localhost;*.local;timestamp.apple.com'; + } return ''; } diff --git a/lib/ui/mobile/menu/bottom_navigation.dart b/lib/ui/mobile/menu/bottom_navigation.dart index c79d4fb..75261dc 100644 --- a/lib/ui/mobile/menu/bottom_navigation.dart +++ b/lib/ui/mobile/menu/bottom_navigation.dart @@ -280,7 +280,8 @@ class SettingPage extends StatelessWidget { children: [ Text(localizations.proxyIgnoreDomain, style: const TextStyle(fontSize: 14)), const SizedBox(height: 3), - Text(isEn ? "Use ';' to separate multiple entries": "多个使用;分割", style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), + Text(isEn ? "Use ';' to separate multiple entries" : "多个使用;分割", + style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), ], ), Padding( @@ -299,10 +300,14 @@ class SettingPage extends StatelessWidget { textInputAction: TextInputAction.done, style: const TextStyle(fontSize: 13), controller: textEditingController, + onSubmitted: (_) { + configuration.proxyPassDomains = textEditingController.text; + proxyServer.configuration.flushConfig(); + }, decoration: const InputDecoration( - contentPadding: EdgeInsets.all(10), - border: OutlineInputBorder(), - ), + contentPadding: EdgeInsets.all(10), + border: OutlineInputBorder(), + ), maxLines: 5, minLines: 1)), // const SizedBox(height: 10), diff --git a/lib/ui/mobile/menu/drawer.dart b/lib/ui/mobile/menu/drawer.dart index c331460..1d71e3d 100644 --- a/lib/ui/mobile/menu/drawer.dart +++ b/lib/ui/mobile/menu/drawer.dart @@ -289,6 +289,10 @@ class _SettingPage extends StatelessWidget { textInputAction: TextInputAction.done, style: const TextStyle(fontSize: 13), controller: textEditingController, + onSubmitted: (_) { + configuration.proxyPassDomains = textEditingController.text; + proxyServer.configuration.flushConfig(); + }, decoration: const InputDecoration( contentPadding: EdgeInsets.all(10), border: OutlineInputBorder()),