From 539ad6a3bbf4e0efad169ce11b70f998c6da7b6e Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkk669@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:42:31 +0900 Subject: [PATCH] Make the server settings customizable --- CustomDNS.xcodeproj/project.pbxproj | 24 ++++- CustomDNS/BundleExtensions.swift | 15 +++ CustomDNS/ContentView.swift | 141 +++++++++++++++++++++---- CustomDNS/CustomDNSApp.swift | 29 +----- CustomDNS/DetailView.swift | 155 ++++++++++++++++++++++++++++ CustomDNS/Info.plist | 2 +- CustomDNS/Presets.swift | 69 +++++++++++++ CustomDNS/Resolver.swift | 133 ++++++++++++++++++++++++ 8 files changed, 515 insertions(+), 53 deletions(-) create mode 100644 CustomDNS/BundleExtensions.swift create mode 100644 CustomDNS/DetailView.swift create mode 100644 CustomDNS/Presets.swift create mode 100644 CustomDNS/Resolver.swift diff --git a/CustomDNS.xcodeproj/project.pbxproj b/CustomDNS.xcodeproj/project.pbxproj index 3dc647a..8937bb8 100644 --- a/CustomDNS.xcodeproj/project.pbxproj +++ b/CustomDNS.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80D4251DC3A20046BAA0 /* DetailView.swift */; }; + 890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80DE251DC6B50046BAA0 /* Presets.swift */; }; 8940023C24ACBD2700EBE74B /* CustomDNSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023B24ACBD2700EBE74B /* CustomDNSApp.swift */; }; 8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023D24ACBD2700EBE74B /* ContentView.swift */; }; 8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940023F24ACBD2800EBE74B /* Assets.xcassets */; }; @@ -14,6 +16,8 @@ 8940024E24ACBD2800EBE74B /* CustomDNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940024D24ACBD2800EBE74B /* CustomDNSTests.swift */; }; 8940025924ACBD2800EBE74B /* CustomDNSUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* CustomDNSUITests.swift */; }; 8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; }; + 8963FDFB251DF1BC00E3DFE7 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* BundleExtensions.swift */; }; + 8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,6 +38,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 890B80D4251DC3A20046BAA0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; + 890B80DE251DC6B50046BAA0 /* Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presets.swift; sourceTree = ""; }; 8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 8940023824ACBD2700EBE74B /* CustomDNS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomDNS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8940023B24ACBD2700EBE74B /* CustomDNSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDNSApp.swift; sourceTree = ""; }; @@ -49,6 +55,8 @@ 8940025A24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8940026624ACBE4900EBE74B /* CustomDNS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CustomDNS.entitlements; sourceTree = ""; }; 8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; + 8963FDFA251DF1BC00E3DFE7 /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; }; + 8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -105,9 +113,13 @@ 8940026624ACBE4900EBE74B /* CustomDNS.entitlements */, 8940023B24ACBD2700EBE74B /* CustomDNSApp.swift */, 8940023D24ACBD2700EBE74B /* ContentView.swift */, + 8963FDFA251DF1BC00E3DFE7 /* BundleExtensions.swift */, + 890B80DE251DC6B50046BAA0 /* Presets.swift */, + 890B80D4251DC3A20046BAA0 /* DetailView.swift */, 8940023F24ACBD2800EBE74B /* Assets.xcassets */, 8940024424ACBD2800EBE74B /* Info.plist */, 8940024124ACBD2800EBE74B /* Preview Content */, + 8986CDCE251D9B3400D947CD /* Resolver.swift */, ); path = CustomDNS; sourceTree = ""; @@ -275,8 +287,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */, + 890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */, 8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */, + 890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */, 8940023C24ACBD2700EBE74B /* CustomDNSApp.swift in Sources */, + 8963FDFB251DF1BC00E3DFE7 /* BundleExtensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -446,9 +462,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.CustomDNS; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTS_MACCATALYST = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -470,9 +486,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.CustomDNS; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTS_MACCATALYST = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/CustomDNS/BundleExtensions.swift b/CustomDNS/BundleExtensions.swift new file mode 100644 index 0000000..93df1df --- /dev/null +++ b/CustomDNS/BundleExtensions.swift @@ -0,0 +1,15 @@ +// +// BundleExtensions.swift +// CustomDNS +// +// Created by Kenta Kubo on 9/25/20. +// + +import Foundation + +extension Bundle { + var displayName: String? { + self.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + ?? self.object(forInfoDictionaryKey: "CFBundleName") as? String + } +} diff --git a/CustomDNS/ContentView.swift b/CustomDNS/ContentView.swift index 2780358..b77ef79 100644 --- a/CustomDNS/ContentView.swift +++ b/CustomDNS/ContentView.swift @@ -5,38 +5,139 @@ // Created by Kenta Kubo on 7/1/20. // -import Combine import NetworkExtension import SwiftUI struct ContentView { + @AppStorage("servers") var servers = Presets.servers + @AppStorage("usedID") var usedID: String? @State var isEnabled = false - var cancellable: Cancellable? - init() { - self.cancellable = Future { resolve in - let manager = NEDNSSettingsManager.shared() - manager.loadFromPreferences { - if let err = $0 { - resolve(.failure(err)) - } else { - resolve(.success(manager.isEnabled)) - } + func addNewDoTServer() { + self.servers.append( + .init( + name: "New", + configuration: .dnsOverTLS(DoTConfiguration()) + ) + ) + } + + func addNewDoHServer() { + self.servers.append( + .init( + name: "New", + configuration: .dnsOverHTTPS(DoHConfiguration()) + ) + ) + } + + func updateStatus() { + let manager = NEDNSSettingsManager.shared() + manager.loadFromPreferences { + if let err = $0 { + print("\(err.localizedDescription)") + } else { + self.isEnabled = manager.isEnabled + } + } + } + + func syncSettings() { + let manager = NEDNSSettingsManager.shared() + manager.loadFromPreferences { loadError in + if let loadError = loadError { + print("\(loadError.localizedDescription)") + return + } + manager.dnsSettings = self.usedID + .flatMap(UUID.init) + .flatMap(self.servers.find) + .map(\.configuration) + .map { $0.toDNSSettings() } + manager.saveToPreferences { saveError in + if let saveError = saveError { + print("\(saveError.localizedDescription)") + return + } + print("saved") } } - .mapError { fatalError("\($0.localizedDescription)") } - .assign(to: \.isEnabled, on: self) } } extension ContentView: View { - @ViewBuilder var body: some View { - if self.isEnabled { - Text("Enabled") - .foregroundColor(.green) - } else { - Text("Disabled") - .foregroundColor(.secondary) + var body: some View { + NavigationView { + List { + Section(header: Text("Servers")) { + ForEach(0..UIApplicationSceneManifest UIApplicationSupportsMultipleScenes - + UIApplicationSupportsIndirectInputEvents diff --git a/CustomDNS/Presets.swift b/CustomDNS/Presets.swift new file mode 100644 index 0000000..dff8a39 --- /dev/null +++ b/CustomDNS/Presets.swift @@ -0,0 +1,69 @@ +// +// Presets.swift +// CustomDNS +// +// Created by Kenta Kubo on 9/25/20. +// + +import Foundation + +enum Presets { + static let servers: Resolvers = [ + .init( + name: "Google Public DNS", + configuration: .dnsOverTLS( + DoTConfiguration( + servers: [ + "8.8.8.8", + "8.8.4.4", + "2001:4860:4860::8888", + "2001:4860:4860::8844", + ], + serverName: "dns.google" + ) + ) + ), + .init( + name: "Google Public DNS", + configuration: .dnsOverHTTPS( + DoHConfiguration( + servers: [ + "8.8.8.8", + "8.8.4.4", + "2001:4860:4860::8888", + "2001:4860:4860::8844", + ], + serverURL: URL(string: "https://dns.google/dns-query") + ) + ) + ), + .init( + name: "1.1.1.1", + configuration: .dnsOverTLS( + DoTConfiguration( + servers: [ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::64", + "2606:4700:4700::6400", + ], + serverName: "cloudflare-dns.com" + ) + ) + ), + .init( + name: "1.1.1.1", + configuration: .dnsOverHTTPS( + DoHConfiguration( + servers: [ + "1.1.1.1", + "1.0.0.1", + "2606:4700:4700::64", + "2606:4700:4700::6400", + ], + serverURL: URL(string: "https://cloudflare-dns.com/dns-query") + ) + ) + ), + ] +} diff --git a/CustomDNS/Resolver.swift b/CustomDNS/Resolver.swift new file mode 100644 index 0000000..37a6e4b --- /dev/null +++ b/CustomDNS/Resolver.swift @@ -0,0 +1,133 @@ +// +// Resolver.swift +// CustomDNS +// +// Created by Kenta Kubo on 9/25/20. +// + +import Foundation +import NetworkExtension + +struct DoTConfiguration { + var servers: [String] = [] + var serverName: String? = nil + + func toDNSSettings() -> NEDNSOverTLSSettings { + let settings = NEDNSOverTLSSettings(servers: self.servers) + settings.serverName = self.serverName + return settings + } +} + +extension DoTConfiguration: Codable {} + +struct DoHConfiguration { + var servers: [String] = [] + var serverURL: URL? = nil + + func toDNSSettings() -> NEDNSOverHTTPSSettings { + let settings = NEDNSOverHTTPSSettings(servers: self.servers) + settings.serverURL = self.serverURL + return settings + } +} + +extension DoHConfiguration: Codable {} + +enum Configuration { + case dnsOverTLS(DoTConfiguration) + case dnsOverHTTPS(DoHConfiguration) + + func toDNSSettings() -> NEDNSSettings { + switch self { + case let .dnsOverTLS(configuration): + return configuration.toDNSSettings() + case let .dnsOverHTTPS(configuration): + return configuration.toDNSSettings() + } + } +} + +extension Configuration: Codable { + enum CodingKeys: String, CodingKey { + case base, dotConfiguration, dohConfiguration + } + + enum Base: String, Codable { + case dnsOverTLS, dnsOverHTTPS + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Self.CodingKeys.self) + let base = try container.decode(Self.Base.self, forKey: .base) + + switch base { + case .dnsOverTLS: + let configuration = try container.decode(DoTConfiguration.self, forKey: .dotConfiguration) + self = .dnsOverTLS(configuration) + case .dnsOverHTTPS: + let configuration = try container.decode(DoHConfiguration.self, forKey: .dohConfiguration) + self = .dnsOverHTTPS(configuration) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Self.CodingKeys.self) + + switch self { + case let .dnsOverTLS(configuration): + try container.encode(Self.Base.dnsOverTLS, forKey: .base) + try container.encode(configuration, forKey: .dotConfiguration) + case let .dnsOverHTTPS(configuration): + try container.encode(Self.Base.dnsOverHTTPS, forKey: .base) + try container.encode(configuration, forKey: .dohConfiguration) + } + } +} + +extension Configuration: CustomStringConvertible { + var description: String { + switch self { + case .dnsOverTLS: return "DNS-over-TLS" + case .dnsOverHTTPS: return "DNS-over-HTTPS" + } + } +} + +struct Resolver { + var id = UUID() + var name: String + var configuration: Configuration +} + +extension Resolver: Identifiable {} + +extension Resolver: Codable {} + +typealias Resolvers = [Resolver] + +extension Resolvers { + func find(by id: UUID) -> Self.Element? { + self.first { $0.id == id } + } +} + +extension Resolvers: RawRepresentable { + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), + let result = try? JSONDecoder().decode(Self.self, from: data) + else { + return nil + } + self = result + } + + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), + let result = String(data: data, encoding: .utf8) + else { + return "[]" + } + return result + } +}