237 lines
6.9 KiB
Swift
237 lines
6.9 KiB
Swift
import Foundation
|
|
import NetworkExtension
|
|
|
|
public enum OnDemandRuleAction: Int, Codable, CaseIterable, Identifiable {
|
|
case connect = 1
|
|
case disconnect = 2
|
|
case evaluateConnection = 3
|
|
case ignore = 4
|
|
|
|
public var id: Int {
|
|
rawValue
|
|
}
|
|
|
|
public var name: String {
|
|
switch self {
|
|
case .connect:
|
|
return NSLocalizedString("Connect", comment: "")
|
|
case .disconnect:
|
|
return NSLocalizedString("Disconnect", comment: "")
|
|
case .evaluateConnection:
|
|
return NSLocalizedString("Evaluate Connection", comment: "")
|
|
case .ignore:
|
|
return NSLocalizedString("Ignore", comment: "")
|
|
}
|
|
}
|
|
|
|
public var actionDescription: String {
|
|
switch self {
|
|
case .connect:
|
|
return NSLocalizedString("Start the VPN connection when conditions match.", comment: "")
|
|
case .disconnect:
|
|
return NSLocalizedString("Stop the VPN connection when conditions match.", comment: "")
|
|
case .evaluateConnection:
|
|
return NSLocalizedString("Evaluate the destination host before deciding to connect.", comment: "")
|
|
case .ignore:
|
|
return NSLocalizedString("Leave the VPN connection in its current state.", comment: "")
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum OnDemandRuleInterfaceType: Int, Codable, Identifiable {
|
|
case any = 0
|
|
#if os(macOS) || os(tvOS)
|
|
case ethernet = 1
|
|
#endif
|
|
case wifi = 2
|
|
#if os(iOS)
|
|
case cellular = 3
|
|
#endif
|
|
|
|
public var id: Int {
|
|
rawValue
|
|
}
|
|
|
|
public var name: String {
|
|
switch self {
|
|
case .any:
|
|
return NSLocalizedString("Any", comment: "")
|
|
#if os(macOS) || os(tvOS)
|
|
case .ethernet:
|
|
return NSLocalizedString("Ethernet", comment: "")
|
|
#endif
|
|
case .wifi:
|
|
return NSLocalizedString("Wi-Fi", comment: "")
|
|
#if os(iOS)
|
|
case .cellular:
|
|
return NSLocalizedString("Cellular", comment: "")
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public static var availableCases: [OnDemandRuleInterfaceType] {
|
|
#if os(iOS)
|
|
return [.any, .wifi, .cellular]
|
|
#elseif os(macOS)
|
|
return [.any, .ethernet, .wifi]
|
|
#elseif os(tvOS)
|
|
return [.any, .ethernet, .wifi]
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public enum EvaluateConnectionRuleAction: Int, Codable, CaseIterable, Identifiable {
|
|
case connectIfNeeded = 1
|
|
case neverConnect = 2
|
|
|
|
public var id: Int {
|
|
rawValue
|
|
}
|
|
|
|
public var name: String {
|
|
switch self {
|
|
case .connectIfNeeded:
|
|
return NSLocalizedString("Connect If Needed", comment: "")
|
|
case .neverConnect:
|
|
return NSLocalizedString("Never Connect", comment: "")
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct EvaluateConnectionRule: Codable, Identifiable, Hashable {
|
|
public var id = UUID()
|
|
public var action: EvaluateConnectionRuleAction = .connectIfNeeded
|
|
public var matchDomains: [String] = []
|
|
public var useDNSServers: [String] = []
|
|
public var probeURL: String = ""
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case action
|
|
case matchDomains
|
|
case useDNSServers
|
|
case probeURL
|
|
}
|
|
|
|
public init() {}
|
|
|
|
public init(action: EvaluateConnectionRuleAction, matchDomains: [String], useDNSServers: [String] = [], probeURL: String = "") {
|
|
self.action = action
|
|
self.matchDomains = matchDomains
|
|
self.useDNSServers = useDNSServers
|
|
self.probeURL = probeURL
|
|
}
|
|
|
|
func toNERule() -> NEEvaluateConnectionRule {
|
|
let neAction: NEEvaluateConnectionRuleAction
|
|
switch action {
|
|
case .connectIfNeeded:
|
|
neAction = .connectIfNeeded
|
|
case .neverConnect:
|
|
neAction = .neverConnect
|
|
}
|
|
let rule = NEEvaluateConnectionRule(matchDomains: matchDomains, andAction: neAction)
|
|
if !useDNSServers.isEmpty {
|
|
rule.useDNSServers = useDNSServers
|
|
}
|
|
if !probeURL.isEmpty, let url = URL(string: probeURL) {
|
|
rule.probeURL = url
|
|
}
|
|
return rule
|
|
}
|
|
}
|
|
|
|
public struct OnDemandRule: Codable, Identifiable, Hashable {
|
|
public var id = UUID()
|
|
public var action: OnDemandRuleAction = .connect
|
|
public var interfaceType: OnDemandRuleInterfaceType = .any
|
|
public var ssidMatch: [String] = []
|
|
public var dnsSearchDomainMatch: [String] = []
|
|
public var dnsServerAddressMatch: [String] = []
|
|
public var probeURL: String = ""
|
|
public var connectionRules: [EvaluateConnectionRule] = []
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case action
|
|
case interfaceType
|
|
case ssidMatch
|
|
case dnsSearchDomainMatch
|
|
case dnsServerAddressMatch
|
|
case probeURL
|
|
case connectionRules
|
|
}
|
|
|
|
public init() {}
|
|
|
|
public init(
|
|
action: OnDemandRuleAction,
|
|
interfaceType: OnDemandRuleInterfaceType = .any,
|
|
ssidMatch: [String] = [],
|
|
dnsSearchDomainMatch: [String] = [],
|
|
dnsServerAddressMatch: [String] = [],
|
|
probeURL: String = "",
|
|
connectionRules: [EvaluateConnectionRule] = []
|
|
) {
|
|
self.action = action
|
|
self.interfaceType = interfaceType
|
|
self.ssidMatch = ssidMatch
|
|
self.dnsSearchDomainMatch = dnsSearchDomainMatch
|
|
self.dnsServerAddressMatch = dnsServerAddressMatch
|
|
self.probeURL = probeURL
|
|
self.connectionRules = connectionRules
|
|
}
|
|
|
|
func toNERule() -> NEOnDemandRule {
|
|
let rule: NEOnDemandRule
|
|
switch action {
|
|
case .connect:
|
|
rule = NEOnDemandRuleConnect()
|
|
case .disconnect:
|
|
rule = NEOnDemandRuleDisconnect()
|
|
case .ignore:
|
|
rule = NEOnDemandRuleIgnore()
|
|
case .evaluateConnection:
|
|
let evalRule = NEOnDemandRuleEvaluateConnection()
|
|
let validRules = connectionRules.filter { !$0.matchDomains.isEmpty }
|
|
if !validRules.isEmpty {
|
|
evalRule.connectionRules = validRules.map { $0.toNERule() }
|
|
}
|
|
rule = evalRule
|
|
}
|
|
|
|
switch interfaceType {
|
|
case .any:
|
|
rule.interfaceTypeMatch = .any
|
|
#if os(macOS) || os(tvOS)
|
|
case .ethernet:
|
|
rule.interfaceTypeMatch = .ethernet
|
|
#endif
|
|
case .wifi:
|
|
rule.interfaceTypeMatch = .wiFi
|
|
#if os(iOS)
|
|
case .cellular:
|
|
rule.interfaceTypeMatch = .cellular
|
|
#endif
|
|
}
|
|
|
|
if !ssidMatch.isEmpty {
|
|
rule.ssidMatch = ssidMatch
|
|
}
|
|
|
|
if !dnsSearchDomainMatch.isEmpty {
|
|
rule.dnsSearchDomainMatch = dnsSearchDomainMatch
|
|
}
|
|
|
|
if !dnsServerAddressMatch.isEmpty {
|
|
rule.dnsServerAddressMatch = dnsServerAddressMatch
|
|
}
|
|
|
|
if !probeURL.isEmpty, let url = URL(string: probeURL) {
|
|
rule.probeURL = url
|
|
}
|
|
|
|
return rule
|
|
}
|
|
}
|