diff --git a/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift b/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift new file mode 100644 index 0000000..db52e16 --- /dev/null +++ b/DNSecure/Extensions/NEEvaluateConnectionRuleAction+Codable.swift @@ -0,0 +1,10 @@ +// +// NEEvaluateConnectionRuleAction+Codable.swift +// DNSecure +// +// Created by Kenta Kubo on 8/31/25. +// + +import NetworkExtension + +extension NEEvaluateConnectionRuleAction: @retroactive Codable {} diff --git a/DNSecure/Models/OnDemandRule.swift b/DNSecure/Models/OnDemandRule.swift new file mode 100644 index 0000000..868c6fc --- /dev/null +++ b/DNSecure/Models/OnDemandRule.swift @@ -0,0 +1,86 @@ +// +// OnDemandRule.swift +// DNSecure +// +// Created by Kenta Kubo on 8/31/25. +// + +import Foundation +import NetworkExtension + +struct OnDemandRule { + var id = UUID() + var name: String + var action: NEOnDemandRuleAction = .connect + var interfaceType: NEOnDemandRuleInterfaceType = .any + var ssidMatch: [String] = [] + var dnsSearchDomainMatch: [String] = [] + var dnsServerAddressMatch: [String] = [] + var probeURL: URL? + var excludedDomains: [String]? +} + +extension OnDemandRule: Identifiable {} + +extension OnDemandRule: Equatable {} + +extension OnDemandRule: Hashable {} + +extension OnDemandRule: Codable {} + +extension [OnDemandRule] { + func toNEOnDemandRules() -> [NEOnDemandRule] { + self.map { rule in + switch rule.action { + case .connect: + let newRule = NEOnDemandRuleConnect() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + 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.isSSIDUsed { + 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.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + newRule.connectionRules = + switch rule.excludedDomains { + case let domains? where !domains.isEmpty: + [.init(matchDomains: domains, andAction: .neverConnect)] + case _: [] + } + return newRule + case .ignore: + let newRule = NEOnDemandRuleIgnore() + newRule.interfaceTypeMatch = rule.interfaceType + if rule.interfaceType.isSSIDUsed { + newRule.ssidMatch = rule.ssidMatch + } + newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch + newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch + newRule.probeURL = rule.probeURL + return newRule + @unknown case _: + preconditionFailure("Unexpected NEOnDemandRuleAction") + } + } + } +} diff --git a/DNSecure/Models/Resolver.swift b/DNSecure/Models/Resolver.swift index 06510d3..082bfcd 100644 --- a/DNSecure/Models/Resolver.swift +++ b/DNSecure/Models/Resolver.swift @@ -106,77 +106,6 @@ extension Configuration: CustomStringConvertible { } } -struct OnDemandRule { - var id = UUID() - var name: String - var action: NEOnDemandRuleAction = .connect - 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.isSSIDUsed { - 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.isSSIDUsed { - 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.isSSIDUsed { - 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.isSSIDUsed { - 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 diff --git a/DNSecure/Views/ExcludedDomainsView.swift b/DNSecure/Views/ExcludedDomainsView.swift new file mode 100644 index 0000000..2582ae6 --- /dev/null +++ b/DNSecure/Views/ExcludedDomainsView.swift @@ -0,0 +1,49 @@ +import SwiftUI + +struct ExcludedDomainsView { + @Binding var domains: [String] +} + +extension ExcludedDomainsView: View { + var body: some View { + Form { + Section { + ForEach(0..