mirror of
https://github.com/kkebo/DNSecure.git
synced 2026-03-11 08:54:36 +00:00
parent
5b28682cc7
commit
99c9d7cbd3
12 changed files with 452 additions and 14 deletions
|
|
@ -9,6 +9,13 @@
|
|||
/* 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 */; };
|
||||
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */; };
|
||||
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */; };
|
||||
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */; };
|
||||
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */; };
|
||||
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */; };
|
||||
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */; };
|
||||
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */; };
|
||||
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023B24ACBD2700EBE74B /* DNSecureApp.swift */; };
|
||||
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023D24ACBD2700EBE74B /* ContentView.swift */; };
|
||||
8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940023F24ACBD2800EBE74B /* Assets.xcassets */; };
|
||||
|
|
@ -16,6 +23,7 @@
|
|||
8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940024D24ACBD2800EBE74B /* DNSecureTests.swift */; };
|
||||
8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* DNSecureUITests.swift */; };
|
||||
8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; };
|
||||
894958AD2548405E009691D5 /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894958AC2548405E009691D5 /* RuleView.swift */; };
|
||||
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */; };
|
||||
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; };
|
||||
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CB922025209DD100B6983C /* HowToActivateView.swift */; };
|
||||
|
|
@ -42,6 +50,13 @@
|
|||
890B80D4251DC3A20046BAA0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = "<group>"; };
|
||||
890B80DE251DC6B50046BAA0 /* Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presets.swift; sourceTree = "<group>"; };
|
||||
8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CaseIterable.swift"; sourceTree = "<group>"; };
|
||||
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CustomStringConvertible.swift"; sourceTree = "<group>"; };
|
||||
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+Codable.swift"; sourceTree = "<group>"; };
|
||||
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+ssidIsUsed.swift"; sourceTree = "<group>"; };
|
||||
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CaseIterable.swift"; sourceTree = "<group>"; };
|
||||
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CustomStringConvertible.swift"; sourceTree = "<group>"; };
|
||||
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+Codable.swift"; sourceTree = "<group>"; };
|
||||
8940023824ACBD2700EBE74B /* DNSecure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSecure.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8940023B24ACBD2700EBE74B /* DNSecureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureApp.swift; sourceTree = "<group>"; };
|
||||
8940023D24ACBD2700EBE74B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -56,6 +71,7 @@
|
|||
8940025A24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8940026624ACBE4900EBE74B /* DNSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNSecure.entitlements; sourceTree = "<group>"; };
|
||||
8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
894958AC2548405E009691D5 /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = "<group>"; };
|
||||
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = "<group>"; };
|
||||
8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
|
||||
89CB922025209DD100B6983C /* HowToActivateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowToActivateView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -93,6 +109,7 @@
|
|||
8940023D24ACBD2700EBE74B /* ContentView.swift */,
|
||||
89CB922025209DD100B6983C /* HowToActivateView.swift */,
|
||||
890B80D4251DC3A20046BAA0 /* DetailView.swift */,
|
||||
894958AC2548405E009691D5 /* RuleView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -110,6 +127,13 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */,
|
||||
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */,
|
||||
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */,
|
||||
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */,
|
||||
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */,
|
||||
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */,
|
||||
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */,
|
||||
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -317,9 +341,17 @@
|
|||
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */,
|
||||
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */,
|
||||
890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */,
|
||||
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */,
|
||||
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */,
|
||||
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */,
|
||||
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */,
|
||||
894958AD2548405E009691D5 /* RuleView.swift in Sources */,
|
||||
890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */,
|
||||
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */,
|
||||
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */,
|
||||
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */,
|
||||
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */,
|
||||
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */,
|
||||
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
14
DNSecure/Extensions/NEOnDemandRuleAction+CaseIterable.swift
Normal file
14
DNSecure/Extensions/NEOnDemandRuleAction+CaseIterable.swift
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// NEOnDemandRuleAction+CaseIterable.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleAction: CaseIterable {
|
||||
public static var allCases: [Self] {
|
||||
[.connect, .disconnect, .evaluateConnection, .ignore]
|
||||
}
|
||||
}
|
||||
10
DNSecure/Extensions/NEOnDemandRuleAction+Codable.swift
Normal file
10
DNSecure/Extensions/NEOnDemandRuleAction+Codable.swift
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// NEOnDemandRuleAction+Codable.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleAction: Codable {}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// NEOnDemandRuleAction+CustomStringConvertible.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleAction: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .connect:
|
||||
return "Apply settings"
|
||||
case .disconnect:
|
||||
return "Do not apply settings"
|
||||
case .evaluateConnection:
|
||||
return "Apply with excluded domains"
|
||||
case .ignore:
|
||||
return "As is"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// NEOnDemandRuleInterfaceType+CaseIterable.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleInterfaceType: CaseIterable {
|
||||
public static var allCases: [Self] {
|
||||
#if os(macOS)
|
||||
return [.any, .ethernet, .wiFi]
|
||||
#else
|
||||
return [.any, .wiFi, .cellular]
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// NEOnDemandRuleInterfaceType+Codable.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleInterfaceType: Codable {}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// NEOnDemandRuleInterfaceType+CustomStringConvertible.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleInterfaceType: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .any:
|
||||
return "Any"
|
||||
#if os(macOS)
|
||||
case .ethernet:
|
||||
return "Ethernet"
|
||||
#endif
|
||||
case .wiFi:
|
||||
return "Wi-Fi"
|
||||
#if os(iOS)
|
||||
case .cellular:
|
||||
return "Cellular"
|
||||
#endif
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// NEOnDemandRuleInterfaceType+ssidIsUsed.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 12/20/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
extension NEOnDemandRuleInterfaceType {
|
||||
var ssidIsUsed: Bool {
|
||||
switch self {
|
||||
case .any, .wiFi:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,10 @@ struct DoTConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
extension DoTConfiguration: Equatable {}
|
||||
|
||||
extension DoTConfiguration: Hashable {}
|
||||
|
||||
extension DoTConfiguration: Codable {}
|
||||
|
||||
struct DoHConfiguration {
|
||||
|
|
@ -32,6 +36,10 @@ struct DoHConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
extension DoHConfiguration: Equatable {}
|
||||
|
||||
extension DoHConfiguration: Hashable {}
|
||||
|
||||
extension DoHConfiguration: Codable {}
|
||||
|
||||
enum Configuration {
|
||||
|
|
@ -48,6 +56,10 @@ enum Configuration {
|
|||
}
|
||||
}
|
||||
|
||||
extension Configuration: Equatable {}
|
||||
|
||||
extension Configuration: Hashable {}
|
||||
|
||||
extension Configuration: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case base, dotConfiguration, dohConfiguration
|
||||
|
|
@ -94,15 +106,106 @@ extension Configuration: CustomStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
struct OnDemandRule {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
var action: NEOnDemandRuleAction = .ignore
|
||||
var interfaceType: NEOnDemandRuleInterfaceType = .any
|
||||
var ssidMatch: [String] = []
|
||||
var dnsSearchDomainMatch: [String] = []
|
||||
var dnsServerAddressMatch: [String] = []
|
||||
var probeURL: URL?
|
||||
}
|
||||
|
||||
extension OnDemandRule: Identifiable {}
|
||||
|
||||
extension OnDemandRule: Equatable {}
|
||||
|
||||
extension OnDemandRule: Hashable {}
|
||||
|
||||
extension OnDemandRule: Codable {}
|
||||
|
||||
extension Array where Self.Element == OnDemandRule {
|
||||
func toNEOnDemandRules() -> [NEOnDemandRule] {
|
||||
self.lazy
|
||||
.map { rule in
|
||||
switch rule.action {
|
||||
case .connect:
|
||||
let newRule = NEOnDemandRuleConnect()
|
||||
newRule.interfaceTypeMatch = rule.interfaceType
|
||||
if rule.interfaceType.ssidIsUsed {
|
||||
newRule.ssidMatch = rule.ssidMatch
|
||||
}
|
||||
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
|
||||
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
|
||||
newRule.probeURL = rule.probeURL
|
||||
return newRule
|
||||
case .disconnect:
|
||||
let newRule = NEOnDemandRuleDisconnect()
|
||||
newRule.interfaceTypeMatch = rule.interfaceType
|
||||
if rule.interfaceType.ssidIsUsed {
|
||||
newRule.ssidMatch = rule.ssidMatch
|
||||
}
|
||||
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
|
||||
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
|
||||
newRule.probeURL = rule.probeURL
|
||||
return newRule
|
||||
case .evaluateConnection:
|
||||
let newRule = NEOnDemandRuleEvaluateConnection()
|
||||
newRule.interfaceTypeMatch = rule.interfaceType
|
||||
if rule.interfaceType.ssidIsUsed {
|
||||
newRule.ssidMatch = rule.ssidMatch
|
||||
}
|
||||
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
|
||||
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
|
||||
newRule.probeURL = rule.probeURL
|
||||
return newRule
|
||||
case .ignore:
|
||||
let newRule = NEOnDemandRuleIgnore()
|
||||
newRule.interfaceTypeMatch = rule.interfaceType
|
||||
if rule.interfaceType.ssidIsUsed {
|
||||
newRule.ssidMatch = rule.ssidMatch
|
||||
}
|
||||
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
|
||||
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
|
||||
newRule.probeURL = rule.probeURL
|
||||
return newRule
|
||||
default:
|
||||
preconditionFailure("Unexpected NEOnDemandRuleAction")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
var configuration: Configuration
|
||||
var onDemandRules: [OnDemandRule] = []
|
||||
}
|
||||
|
||||
extension Resolver: Identifiable {}
|
||||
|
||||
extension Resolver: Codable {}
|
||||
extension Resolver: Equatable {}
|
||||
|
||||
extension Resolver: Hashable {}
|
||||
|
||||
extension Resolver: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, configuration, onDemandRules
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(UUID.self, forKey: .id)
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
self.configuration = try container.decode(Configuration.self, forKey: .configuration)
|
||||
self.onDemandRules = try container.decodeIfPresent(
|
||||
[OnDemandRule].self,
|
||||
forKey: .onDemandRules
|
||||
) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
typealias Resolvers = [Resolver]
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,17 @@ struct ContentView {
|
|||
|
||||
func removeServers(at indexSet: IndexSet) {
|
||||
if let current = self.selection, indexSet.contains(where: { $0 <= current }) {
|
||||
self.selection = nil
|
||||
// FIXME: This is a workaround not to crash on deletion.
|
||||
self.selection = -1
|
||||
}
|
||||
if indexSet.map({ self.servers[$0].id.uuidString }).contains(self.usedID) {
|
||||
self.removeSettings()
|
||||
}
|
||||
self.servers.remove(atOffsets: indexSet)
|
||||
// FIXME: This is a workaround not to crash on deletion.
|
||||
// Wait for closing DetailView.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
|
||||
self.servers.remove(atOffsets: indexSet)
|
||||
}
|
||||
}
|
||||
|
||||
func moveServers(from src: IndexSet, to dst: Int) {
|
||||
|
|
@ -76,6 +81,7 @@ struct ContentView {
|
|||
#if !targetEnvironment(simulator)
|
||||
let manager = NEDNSSettingsManager.shared()
|
||||
manager.dnsSettings = server.configuration.toDNSSettings()
|
||||
manager.onDemandRules = server.onDemandRules.toNEOnDemandRules()
|
||||
manager.saveToPreferences { saveError in
|
||||
if let saveError = saveError as NSError? {
|
||||
guard saveError.domain != "NEConfigurationErrorDomain"
|
||||
|
|
@ -125,21 +131,17 @@ extension ContentView: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
NavigationLink(
|
||||
"Instructions",
|
||||
destination: HowToActivateView(isSheet: false),
|
||||
tag: -1,
|
||||
selection: self.$selection
|
||||
)
|
||||
Section(header: Text("Servers")) {
|
||||
ForEach(0..<self.servers.count, id: \.self) { i in
|
||||
NavigationLink(
|
||||
destination: DetailView(
|
||||
server: .init(
|
||||
get: { self.servers[i] },
|
||||
set: {
|
||||
self.servers[i] = $0
|
||||
|
||||
let server = self.servers[i]
|
||||
if server.id.uuidString == self.usedID {
|
||||
self.saveSettings(of: server)
|
||||
}
|
||||
}
|
||||
),
|
||||
server: self.$servers[i],
|
||||
isOn: .init(
|
||||
get: {
|
||||
self.usedID == self.servers[i].id.uuidString
|
||||
|
|
@ -221,6 +223,15 @@ extension ContentView: View {
|
|||
.onChange(of: self.scenePhase) { phase in
|
||||
if phase == .active {
|
||||
self.updateStatus()
|
||||
} else {
|
||||
// FIXME: This is a workaround for self.$severs[i].
|
||||
// That cannot save settings as soon as it is modified.
|
||||
guard let id = self.usedID,
|
||||
let uuid = UUID(uuidString: id),
|
||||
let server = self.servers.find(by: uuid) else {
|
||||
return
|
||||
}
|
||||
self.saveSettings(of: server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ import SwiftUI
|
|||
struct DetailView {
|
||||
@Binding var server: Resolver
|
||||
@Binding var isOn: Bool
|
||||
|
||||
func binding(for rule: OnDemandRule) -> Binding<OnDemandRule> {
|
||||
guard let index = self.server.onDemandRules.firstIndex(of: rule) else {
|
||||
preconditionFailure("Can't find rule in array")
|
||||
}
|
||||
return self.$server.onDemandRules[index]
|
||||
}
|
||||
}
|
||||
|
||||
extension DetailView: View {
|
||||
|
|
@ -157,6 +164,25 @@ extension DetailView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
Section(
|
||||
header: EditButton()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.overlay(Text("On Demand Rules"), alignment: .leading)
|
||||
) {
|
||||
ForEach(self.server.onDemandRules) { rule in
|
||||
NavigationLink(
|
||||
rule.name,
|
||||
destination: RuleView(rule: self.binding(for: rule))
|
||||
)
|
||||
}
|
||||
.onDelete { self.server.onDemandRules.remove(atOffsets: $0) }
|
||||
.onMove { self.server.onDemandRules.move(fromOffsets: $0, toOffset: $1) }
|
||||
Button("Add New Rule") {
|
||||
self.server.onDemandRules
|
||||
.append(OnDemandRule(name: "New Rule"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(self.server.name)
|
||||
}
|
||||
|
|
|
|||
141
DNSecure/Views/RuleView.swift
Normal file
141
DNSecure/Views/RuleView.swift
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// RuleView.swift
|
||||
// DNSecure
|
||||
//
|
||||
// Created by Kenta Kubo on 10/27/20.
|
||||
//
|
||||
|
||||
import NetworkExtension
|
||||
import SwiftUI
|
||||
|
||||
struct RuleView {
|
||||
@Binding var rule: OnDemandRule
|
||||
}
|
||||
|
||||
extension RuleView: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
HStack {
|
||||
Text("Name")
|
||||
TextField("Name", text: self.$rule.name)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
Picker("Action", selection: self.$rule.action) {
|
||||
ForEach(NEOnDemandRuleAction.allCases, id: \.self) {
|
||||
Text($0.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(
|
||||
header: Text("Interface Type Match"),
|
||||
footer: Text("If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches.")
|
||||
) {
|
||||
Picker("Interface Type", selection: self.$rule.interfaceType) {
|
||||
ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) {
|
||||
Text($0.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.rule.interfaceType.ssidIsUsed {
|
||||
Section(
|
||||
header: EditButton()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.overlay(Text("SSID Match"), alignment: .leading),
|
||||
footer: Text("If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
|
||||
) {
|
||||
ForEach(0..<self.rule.ssidMatch.count, id: \.self) { i in
|
||||
TextField(
|
||||
"SSID",
|
||||
// self.$rule.ssidMatch[i] causes crash on deletion
|
||||
text: .init(
|
||||
get: { self.rule.ssidMatch[i] },
|
||||
set: { self.rule.ssidMatch[i] = $0 }
|
||||
)
|
||||
)
|
||||
}
|
||||
.onDelete { self.rule.ssidMatch.remove(atOffsets: $0) }
|
||||
.onMove { self.rule.ssidMatch.move(fromOffsets: $0, toOffset: $1) }
|
||||
Button("Add SSID") {
|
||||
self.rule.ssidMatch.append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(
|
||||
header: EditButton()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.overlay(Text("DNS Search Domain Match"), alignment: .leading),
|
||||
footer: Text("If the current default search domain is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
|
||||
) {
|
||||
ForEach(0..<self.rule.dnsSearchDomainMatch.count, id: \.self) { i in
|
||||
TextField(
|
||||
"Search Domain",
|
||||
// self.$rule.dnsSearchDomainMatch[i] causes crash on deletion
|
||||
text: .init(
|
||||
get: { self.rule.dnsSearchDomainMatch[i] },
|
||||
set: { self.rule.dnsSearchDomainMatch[i] = $0 }
|
||||
)
|
||||
)
|
||||
}
|
||||
.onDelete { self.rule.dnsSearchDomainMatch.remove(atOffsets: $0) }
|
||||
.onMove { self.rule.dnsSearchDomainMatch.move(fromOffsets: $0, toOffset: $1) }
|
||||
Button("Add DNS Search Domain") {
|
||||
self.rule.dnsSearchDomainMatch.append("")
|
||||
}
|
||||
}
|
||||
|
||||
Section(
|
||||
header: EditButton()
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.overlay(Text("DNS Server Address Match"), alignment: .leading),
|
||||
footer: Text("If each of the current default DNS servers is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
|
||||
) {
|
||||
ForEach(0..<self.rule.dnsServerAddressMatch.count, id: \.self) { i in
|
||||
TextField(
|
||||
"IP Address",
|
||||
// self.$rule.dnsServerAddressMatch[i] causes crash on deletion
|
||||
text: .init(
|
||||
get: { self.rule.dnsServerAddressMatch[i] },
|
||||
set: { self.rule.dnsServerAddressMatch[i] = $0 }
|
||||
)
|
||||
)
|
||||
}
|
||||
.onDelete { self.rule.dnsServerAddressMatch.remove(atOffsets: $0) }
|
||||
.onMove { self.rule.dnsServerAddressMatch.move(fromOffsets: $0, toOffset: $1) }
|
||||
Button("Add DNS Server Address") {
|
||||
self.rule.dnsServerAddressMatch.append("")
|
||||
}
|
||||
}
|
||||
|
||||
Section(
|
||||
header: Text("Probe URL Match"),
|
||||
footer: Text("If a request sent to this URL results in a HTTP 200 OK response and all of the other conditions in the rule match, then the rule matches. If you don't want to use this rule, leave it empty.")
|
||||
) {
|
||||
HStack {
|
||||
Text("Probe URL")
|
||||
TextField(
|
||||
"URL",
|
||||
text: .init(
|
||||
get: { self.rule.probeURL?.absoluteString ?? "" },
|
||||
set: { self.rule.probeURL = URL(string: $0) }
|
||||
)
|
||||
)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(self.rule.name)
|
||||
}
|
||||
}
|
||||
|
||||
struct RuleView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RuleView(rule: .constant(OnDemandRule(name: "Preview Rule")))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue