diff --git a/ClashX.xcodeproj/project.pbxproj b/ClashX.xcodeproj/project.pbxproj index ec15054..15e052f 100644 --- a/ClashX.xcodeproj/project.pbxproj +++ b/ClashX.xcodeproj/project.pbxproj @@ -13,9 +13,10 @@ 018F88F9286DD0CB004DD0F7 /* DualTitleMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018F88F8286DD0CB004DD0F7 /* DualTitleMenuItem.swift */; }; 01943259287D19BC008CC51A /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01943258287D19BC008CC51A /* ClashRuleProvider.swift */; }; 019A239628657A7A00AE5698 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019A239528657A7A00AE5698 /* main.swift */; }; + 01A645D3292C769D00B37FA2 /* DNSConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A645D2292C759100B37FA2 /* DNSConfiguration.swift */; }; 01B009AE2854533300B93618 /* geoip.dat.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B009AC2854533200B93618 /* geoip.dat.gz */; }; 01B009AF2854533300B93618 /* geosite.dat.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01B009AD2854533300B93618 /* geosite.dat.gz */; }; - 01B30C43291CE18F0081C4F7 /* MetaDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B30C41291C98930081C4F7 /* MetaDNS.swift */; }; + 01BC9ABE2928EB5A00F9B177 /* MetaDNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */; }; 01C1462A28962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz in Resources */ = {isa = PBXBuildFile; fileRef = 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */; }; 185CBAEDFE986E6E1B836359 /* libPods-ClashX Meta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AA63125BBF03DC1A291D3351 /* libPods-ClashX Meta.a */; }; 4913C82321157D0200F6B87C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913C82221157D0200F6B87C /* Notification.swift */; }; @@ -140,9 +141,10 @@ 018F88F8286DD0CB004DD0F7 /* DualTitleMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DualTitleMenuItem.swift; sourceTree = ""; }; 01943258287D19BC008CC51A /* ClashRuleProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = ""; }; 019A239528657A7A00AE5698 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 01A645D2292C759100B37FA2 /* DNSConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSConfiguration.swift; sourceTree = ""; }; 01B009AC2854533200B93618 /* geoip.dat.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = geoip.dat.gz; sourceTree = ""; }; 01B009AD2854533300B93618 /* geosite.dat.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = geosite.dat.gz; sourceTree = ""; }; - 01B30C41291C98930081C4F7 /* MetaDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaDNS.swift; sourceTree = ""; }; + 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaDNS.swift; sourceTree = ""; }; 01C1462928962E4E00346AF3 /* com.metacubex.ClashX.ProxyConfigHelper.meta.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = com.metacubex.ClashX.ProxyConfigHelper.meta.gz; sourceTree = ""; }; 3F86DA2DA3CC14731BE1ABF7 /* Pods-ClashX Meta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX Meta.release.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX Meta/Pods-ClashX Meta.release.xcconfig"; sourceTree = ""; }; 4913C82221157D0200F6B87C /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; @@ -464,7 +466,6 @@ 49761DA521C9490400AE13EF /* Resources */, 49CF3B3A20CD783A001EBF94 /* Support Files */, 49CF3B2020CD7463001EBF94 /* AppDelegate.swift */, - 01B30C41291C98930081C4F7 /* MetaDNS.swift */, 49CF3B2620CD7465001EBF94 /* Main.storyboard */, 49CF3B2920CD7465001EBF94 /* Info.plist */, 49CF3B2A20CD7465001EBF94 /* ClashX.entitlements */, @@ -529,6 +530,8 @@ 498960722340F21C00AFB7EC /* com.metacubex.ClashX.ProxyConfigHelper.entitlements */, 019A239528657A7A00AE5698 /* main.swift */, 0162E74E2864B819007218A6 /* MetaTask.swift */, + 01BC9ABD2928E5C600F9B177 /* MetaDNS.swift */, + 01A645D2292C759100B37FA2 /* DNSConfiguration.swift */, F935B2F12307C802009E4D33 /* Helper-Launchd.plist */, F935B2EA2307B6BA009E4D33 /* Helper-Info.plist */, F935B2F22307CD32009E4D33 /* ProxyConfigHelper.h */, @@ -710,7 +713,6 @@ F935B2FC23085515009E4D33 /* SystemProxyManager.swift in Sources */, 495A44D320D267D000888A0A /* LaunchAtLogin.swift in Sources */, 4929F67F258CE04700A435F6 /* Settings.swift in Sources */, - 01B30C43291CE18F0081C4F7 /* MetaDNS.swift in Sources */, 493AEAE3221AE3420016FE98 /* AppVersionUtil.swift in Sources */, 49CF3B2120CD7463001EBF94 /* AppDelegate.swift in Sources */, 496BDEE021196F1E00C5207F /* Logger.swift in Sources */, @@ -774,9 +776,11 @@ buildActionMask = 2147483647; files = ( F935B2F42307CD32009E4D33 /* ProxyConfigHelper.m in Sources */, + 01A645D3292C769D00B37FA2 /* DNSConfiguration.swift in Sources */, 0162E74F2864B819007218A6 /* MetaTask.swift in Sources */, 019A239628657A7A00AE5698 /* main.swift in Sources */, 491E6203258A424D00313AEF /* CommonUtils.m in Sources */, + 01BC9ABE2928EB5A00F9B177 /* MetaDNS.swift in Sources */, F935B2FA23083EE6009E4D33 /* ProxySettingTool.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ClashX/AppDelegate.swift b/ClashX/AppDelegate.swift index 73570c7..fd10b3f 100644 --- a/ClashX/AppDelegate.swift +++ b/ClashX/AppDelegate.swift @@ -72,8 +72,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { var dashboardWindowController: ClashWebViewWindowController? - let metaDNS = MetaDNS() - func applicationWillFinishLaunching(_ notification: Notification) { signal(SIGPIPE, SIG_IGN) // crash recorder @@ -138,8 +136,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var shouldWait = false PrivilegedHelperManager.shared.helper()?.stopMeta() - - metaDNS.updateTunState(false) + PrivilegedHelperManager.shared.helper()?.updateTun(with: false) if ConfigManager.shared.proxyPortAutoSet && !ConfigManager.shared.isProxySetByOtherVariable.value || NetworkChangeNotifier.isCurrentSystemSetToClash(looser: true) || NetworkChangeNotifier.hasInterfaceProxySetToClash() { @@ -241,7 +238,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { .distinctUntilChanged() .bind { _ in let isTunMode = ConfigManager.shared.isTunModeVariable.value - self.metaDNS.updateTunState(isTunMode) + PrivilegedHelperManager.shared.helper()?.updateTun(with: isTunMode) + Logger.log("tun state updated,new: \(isTunMode)") }.disposed(by: disposeBag) Observable diff --git a/ClashX/MetaDNS.swift b/ClashX/MetaDNS.swift deleted file mode 100644 index 859594a..0000000 --- a/ClashX/MetaDNS.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// MetaDNS.swift -// ClashX - -import Cocoa - -class MetaDNS: NSObject { - - var defaultDNS = "198.18.0.2" - var savedDNS: [String]? - - func updateTunState(_ isTun: Bool) { - if isTun { - if savedDNS == nil { - let dns = getDNS() - if dns.count == 1, dns[0] == defaultDNS { - savedDNS = [] - } else { - savedDNS = dns - } - } - setDNS() - } else { - if savedDNS == nil || savedDNS!.count == 0 { - setDNS([]) - } else if let dns = savedDNS { - setDNS(dns) - } - } - } - - func getDNS() -> [String] { - let re = runCommand("/usr/sbin/networksetup", args: [ - "-getdnsservers", - "\(networkServiceName())" - ]) - - if re.contains("There aren't any DNS Servers") { - return [] - } - - return re.split(separator: "\n").map(String.init) - } - - func setDNS(_ dns: [String] = ["198.18.0.2"]) { - var args = [ - "-setdnsservers", - "\(networkServiceName())" - ] - if dns.count > 0 { - args.append(contentsOf: dns) - } else { - args.append("Empty") - } - _ = runCommand("/usr/sbin/networksetup", args: args) - } - - func networkServiceName() -> String { - // https://apple.stackexchange.com/a/432170 - - runCommand("/bin/bash", args: ["-c", "networksetup -listnetworkserviceorder | awk -v DEV=$(/usr/sbin/scutil --nwi | awk -F': ' '/Network interfaces/ {print $2;exit;}') -F': |,' '$0~ DEV {print $2;exit;}'"]) - } - - func runCommand(_ path: String, args: [String]) -> String { - let proc = Process() - proc.executableURL = .init(fileURLWithPath: path) - proc.arguments = args - let pipe = Pipe() - proc.standardOutput = pipe - do { - try proc.run() - } catch let error { - Logger.log(error.localizedDescription) - return "" - } - proc.waitUntilExit() - let data = pipe.fileHandleForReading.readDataToEndOfFile() - - guard proc.terminationStatus == 0, - var out = String(data: data, encoding: .utf8) else { - return "" - } - if out.last == "\n" { - out.removeLast() - } - - return out - } -} diff --git a/ProxyConfigHelper/DNSConfiguration.swift b/ProxyConfigHelper/DNSConfiguration.swift new file mode 100644 index 0000000..4017ab1 --- /dev/null +++ b/ProxyConfigHelper/DNSConfiguration.swift @@ -0,0 +1,104 @@ +// +// DNSConfiguration.swift +// ClashX + + + +import Cocoa +import SystemConfiguration + +// https://github.com/ivanstegic/menu-bar-dns/blob/main/Menu%20Bar%20DNS/Menu%20Bar%20DNS/DNSConfiguration.swift + +class DNSConfiguration: NSObject { + + static let DNSConfigurationTypeKey = Bundle.main.bundleIdentifier! + ".dns" + static let ServiceTypeWiFi = "IEEE80211" + static let ServiceTypeEthernet = "Ethernet" + + static func getnterfaceTypeByServiceIDs(_ services: Array) throws -> Dictionary { + let allServicesIDsAndInterfaceType = try services.map { service -> (String, String) in + guard + let id = SCNetworkServiceGetServiceID(service) as String?, + let interface = SCNetworkServiceGetInterface(service), + let interfaceType = SCNetworkInterfaceGetInterfaceType(interface) as String? + else { + throw SCCopyLastError() + } + return (id, interfaceType) + } + return Dictionary(uniqueKeysWithValues: allServicesIDsAndInterfaceType) + } + + static func isConnectedService(_ service : SCNetworkService) throws -> Bool { + + guard + let id = SCNetworkServiceGetServiceID(service) as String? + else { + throw SCCopyLastError() + } + + let dynmaicStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault, "DNSSETTING" as CFString, nil, nil) + let serviceStateIPv4Key = "State:/Network/Service/\(id)/IPv4" as CFString + let value = SCDynamicStoreCopyValue(dynmaicStore, serviceStateIPv4Key) as CFPropertyList? + return value != nil + } + + static func getDNSForServiceID(_ serviceID:String) -> [String] { + let serviceDNSKey = "State:/Network/Service/\(serviceID)/DNS" as CFString + let serviceSetupDNSKey = "Setup:/Network/Service/\(serviceID)/DNS" as CFString + let dynmaicStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault, "DNSSETTING" as CFString, nil, nil) + var allDNSIPAddresses : Array = [] + + let dynamicPlist = SCDynamicStoreCopyValue(dynmaicStore, serviceDNSKey) + let manualAddressPlist = SCDynamicStoreCopyValue(dynmaicStore, serviceSetupDNSKey) + + if let dnsValues = manualAddressPlist?[kSCPropNetDNSServerAddresses] as? [String] { + allDNSIPAddresses += dnsValues + } + /* + if let dhcpValues = dynamicPlist?[kSCPropNetDNSServerAddresses] as? [String] { + let uniqueValues = Array(Set(dhcpValues)) + for dhcpValue in uniqueValues { + let newvalue = dhcpValue.appending(" (via DHCP)") + allDNSIPAddresses.append(newvalue) + } + } + */ + return allDNSIPAddresses + } + + static func getAddresses() -> (Array, Array) { + var ethernetDNSAddresses : Array = [] + var WiFiDNSAddresses : Array = [] + + do { + + let prefs = SCPreferencesCreate( + nil, DNSConfigurationTypeKey as NSString, + nil + ) as SCPreferences? + let allServicesCF = SCNetworkServiceCopyAll(prefs!) + let allServices = allServicesCF as? [SCNetworkService] + + let allConnectedServices = try allServices?.filter({ (service) -> Bool in + return try isConnectedService(service) + }) + let serviceTypeByIDs = try getnterfaceTypeByServiceIDs(allConnectedServices!) as Dictionary? + for (id, type) in serviceTypeByIDs! { + switch (type) { + case ServiceTypeWiFi: + WiFiDNSAddresses += getDNSForServiceID(id) + case ServiceTypeEthernet: + ethernetDNSAddresses += getDNSForServiceID(id) + default: + print("") + } + } + } + catch { + return ([], []) + } + return (ethernetDNSAddresses, WiFiDNSAddresses) + } + +} diff --git a/ProxyConfigHelper/Helper-Info.plist b/ProxyConfigHelper/Helper-Info.plist index d5edabd..5d10391 100755 --- a/ProxyConfigHelper/Helper-Info.plist +++ b/ProxyConfigHelper/Helper-Info.plist @@ -9,9 +9,9 @@ CFBundleName com.metacubex.ClashX.ProxyConfigHelper CFBundleShortVersionString - 1.5 + 1.6 CFBundleVersion - 6 + 7 SMAuthorizedClients anchor apple generic and identifier "com.metacubex.ClashX.ProxyConfigHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = MEWHFZ92DY) diff --git a/ProxyConfigHelper/MetaDNS.swift b/ProxyConfigHelper/MetaDNS.swift new file mode 100644 index 0000000..cfbbb1c --- /dev/null +++ b/ProxyConfigHelper/MetaDNS.swift @@ -0,0 +1,155 @@ +// +// MetaDNS.swift +// ClashX + + + +import Cocoa +import SystemConfiguration + +// https://github.com/zhuhaow/Specht2/blob/main/app/me.zhuhaow.Specht2.proxy-helper/ProxyHelper.swift + +class MetaDNS: NSObject { + + var savedDns = [String: [String]]() + let defaultDNS = "198.18.0.2" + + let authRef: AuthorizationRef + + override init() { + var auth: AuthorizationRef? + let authFlags: AuthorizationFlags = [.extendRights, .interactionAllowed, .preAuthorize] + + let authErr = AuthorizationCreate(nil, nil, authFlags, &auth) + + if authErr != noErr { + NSLog("Error: Failed to create administration authorization due to error \(authErr).") + } + + if auth == nil { + NSLog("Error: No authorization has been granted to modify network configuration.") + } + + authRef = auth! + + super.init() + } + + deinit { + AuthorizationFree(authRef, AuthorizationFlags()) + } + + @objc func updateDns() { + let dns = getAllDns() + dns.forEach { + if $0.value.count == 1, + $0.value[0] == defaultDNS { + savedDns[$0.key] = [] + } else { + savedDns[$0.key] = $0.value + } + } + + let dnsDic = dns.reduce(into: [:]) { + $0[$1.key] = [defaultDNS] + } + + updateDNSConfigure(dnsDic) + } + + @objc func revertDns() { + updateDNSConfigure(savedDns) + savedDns.removeAll() + } + + func getAllDns() -> [String: [String]] { + var re = [String: [String]]() + + guard let prefs = SCPreferencesCreate(nil, "ClashX" as CFString, nil), + let values = SCPreferencesGetValue(prefs, kSCPrefNetworkServices) as? [String: AnyObject] else { + return re + } + + values.reduce(into: [:]) { + $0[$1.key] = $1.value.value(forKeyPath: "Interface.Hardware") as? String + }.filter { + ["AirPort", "Wi-Fi", "Ethernet"].contains($0.value) + }.forEach { + re[$0.key] = DNSConfiguration.getDNSForServiceID($0.key) + } + + return re + } + + @objc func flushDnsCache() { + CommonUtils.runCommand("/usr/bin/killall", args: ["-HUP", "mDNSResponder"]) + + print("flushDnsCache") + } + + private func updateDNSConfigure(_ dnsDic: [String: [String]]) { + + guard let prefRef = SCPreferencesCreateWithAuthorization( + nil, + "com.metacubex.ClashX.ProxyConfigHelper.config" as CFString, + nil, + authRef) else { + NSLog("Error: Failed to obtain preference ref.") + return + } + + guard SCPreferencesLock(prefRef, true) else { + NSLog("Error: Failed to obtain lock to preference.") + return + } + + defer { + SCPreferencesUnlock(prefRef) + } + + guard let networks = SCNetworkSetCopyCurrent(prefRef), + let services = SCNetworkSetCopyServices(networks) as? [SCNetworkService] else { + NSLog("Error: Failed to load network services.") + return + } + + let type = kSCNetworkProtocolTypeDNS + + services.forEach { service in + guard let id = SCNetworkServiceGetServiceID(service) as? String, + let dns = dnsDic[id] else { + return + } + + + guard let protoc = SCNetworkServiceCopyProtocol(service, type) else { + NSLog("Error: Failed to obtain \(type) settings for \(SCNetworkServiceGetName(service)!)") + return + } + + let config = SCNetworkProtocolGetConfiguration(protoc) + + var dic = (config as NSDictionary?)?.mutableCopy() as? NSMutableDictionary ?? NSMutableDictionary() + + dic["ServerAddresses"] = dns + + guard SCNetworkProtocolSetConfiguration(protoc, dic as CFDictionary) else { + NSLog("Error: Failed to set \(type) settings for \(SCNetworkServiceGetName(service)!)") + return + } + + NSLog("Set \(type) settings for \(SCNetworkServiceGetName(service)!)") + } + + + guard SCPreferencesCommitChanges(prefRef) else { + NSLog("Error: Failed to commit preference change") + return + } + + guard SCPreferencesApplyChanges(prefRef) else { + NSLog("Error: Failed to apply preference change") + return + } + } +} diff --git a/ProxyConfigHelper/ProxyConfigHelper.m b/ProxyConfigHelper/ProxyConfigHelper.m index 145b64c..d509d69 100644 --- a/ProxyConfigHelper/ProxyConfigHelper.m +++ b/ProxyConfigHelper/ProxyConfigHelper.m @@ -26,6 +26,7 @@ ProxyConfigRemoteProcessProtocol @property (nonatomic, assign) BOOL shouldQuit; @property (nonatomic, strong) MetaTask *metaTask; +@property (nonatomic, strong) MetaDNS *metaDNS; @end @@ -38,6 +39,7 @@ ProxyConfigRemoteProcessProtocol self.listener = [[NSXPCListener alloc] initWithMachServiceName:@"com.metacubex.ClashX.ProxyConfigHelper"]; self.listener.delegate = self; self.metaTask = [MetaTask new]; + self.metaDNS = [MetaDNS new]; } return self; } @@ -158,4 +160,15 @@ ProxyConfigRemoteProcessProtocol [self.metaTask getUsedPorts:reply]; } +- (void)updateTunWith:(BOOL)state { + dispatch_async(dispatch_get_main_queue(), ^{ + if (state) { + [self.metaDNS updateDns]; + } else { + [self.metaDNS revertDns]; + } + [self.metaDNS flushDnsCache]; + }); +} + @end diff --git a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h index ccff71f..fbca531 100644 --- a/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h +++ b/ProxyConfigHelper/ProxyConfigRemoteProcessProtocol.h @@ -27,6 +27,8 @@ typedef void(^dictReplyBlock)(NSDictionary *); - (void)getUsedPorts:(stringReplyBlock)reply; +- (void)updateTunWith:(BOOL)state; + - (void)stopMeta; - (void)getVersion:(stringReplyBlock)reply; diff --git a/ProxyConfigHelper/com.metacubex.ClashX.ProxyConfigHelper-Bridging-Header.h b/ProxyConfigHelper/com.metacubex.ClashX.ProxyConfigHelper-Bridging-Header.h index 9591dac..9e1566c 100644 --- a/ProxyConfigHelper/com.metacubex.ClashX.ProxyConfigHelper-Bridging-Header.h +++ b/ProxyConfigHelper/com.metacubex.ClashX.ProxyConfigHelper-Bridging-Header.h @@ -6,3 +6,4 @@ #import "ProxyConfigHelper.h" #import "ProxyConfigRemoteProcessProtocol.h" #import "CommonUtils.h" +#import "ProxySettingTool.h"