Model, Tunnels manager: Rewrite the model for VPN-on-demand
The VPN-on-demand settings should not be part of the tunnel configuration. Rather, the onDemandRules stored in the tunnel provider configuration serve as the one place where the VPN-on-demand settings are stored. Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
struct ActivateOnDemandSetting {
|
||||
var isActivateOnDemandEnabled: Bool
|
||||
var activateOnDemandOption: ActivateOnDemandOption
|
||||
}
|
||||
|
||||
enum ActivateOnDemandOption {
|
||||
case none // Valid only when isActivateOnDemandEnabled is false
|
||||
case useOnDemandOverWifiOrCellular
|
||||
case useOnDemandOverWifiOnly
|
||||
case useOnDemandOverCellularOnly
|
||||
}
|
||||
|
||||
extension ActivateOnDemandSetting {
|
||||
func apply(on tunnelProviderManager: NETunnelProviderManager) {
|
||||
tunnelProviderManager.isOnDemandEnabled = isActivateOnDemandEnabled
|
||||
let rules: [NEOnDemandRule]?
|
||||
let connectRule = NEOnDemandRuleConnect()
|
||||
let disconnectRule = NEOnDemandRuleDisconnect()
|
||||
switch (activateOnDemandOption) {
|
||||
case .none:
|
||||
rules = nil
|
||||
case .useOnDemandOverWifiOrCellular:
|
||||
rules = [connectRule]
|
||||
case .useOnDemandOverWifiOnly:
|
||||
connectRule.interfaceTypeMatch = .wiFi
|
||||
disconnectRule.interfaceTypeMatch = .cellular
|
||||
rules = [connectRule, disconnectRule]
|
||||
case .useOnDemandOverCellularOnly:
|
||||
connectRule.interfaceTypeMatch = .cellular
|
||||
disconnectRule.interfaceTypeMatch = .wiFi
|
||||
rules = [connectRule, disconnectRule]
|
||||
}
|
||||
tunnelProviderManager.onDemandRules = rules
|
||||
}
|
||||
|
||||
init(from tunnelProviderManager: NETunnelProviderManager) {
|
||||
let rules = tunnelProviderManager.onDemandRules ?? []
|
||||
let activateOnDemandOption: ActivateOnDemandOption
|
||||
switch (rules.count) {
|
||||
case 0:
|
||||
activateOnDemandOption = .none
|
||||
case 1:
|
||||
let rule = rules[0]
|
||||
precondition(rule.action == .connect)
|
||||
activateOnDemandOption = .useOnDemandOverWifiOrCellular
|
||||
case 2:
|
||||
let connectRule = rules.first(where: { $0.action == .connect })!
|
||||
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
|
||||
if (connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == .cellular) {
|
||||
activateOnDemandOption = .useOnDemandOverWifiOnly
|
||||
} else if (connectRule.interfaceTypeMatch == .cellular && disconnectRule.interfaceTypeMatch == .wiFi) {
|
||||
activateOnDemandOption = .useOnDemandOverCellularOnly
|
||||
} else {
|
||||
fatalError("Unexpected onDemandRules set on tunnel provider manager")
|
||||
}
|
||||
default:
|
||||
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
|
||||
}
|
||||
self.activateOnDemandOption = activateOnDemandOption
|
||||
if (activateOnDemandOption == .none) {
|
||||
self.isActivateOnDemandEnabled = false
|
||||
} else {
|
||||
self.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ActivateOnDemandSetting {
|
||||
static var defaultSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: false, activateOnDemandOption: .none)
|
||||
}
|
||||
@@ -54,7 +54,9 @@ class TunnelsManager {
|
||||
#endif
|
||||
}
|
||||
|
||||
func add(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (TunnelContainer?, TunnelManagementError?) -> Void) {
|
||||
func add(tunnelConfiguration: TunnelConfiguration,
|
||||
activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting,
|
||||
completionHandler: @escaping (TunnelContainer?, TunnelManagementError?) -> Void) {
|
||||
let tunnelName = tunnelConfiguration.interface.name
|
||||
if tunnelName.isEmpty {
|
||||
completionHandler(nil, TunnelManagementError.tunnelAlreadyExistsWithThatName)
|
||||
@@ -72,13 +74,7 @@ class TunnelsManager {
|
||||
tunnelProviderManager.localizedDescription = tunnelName
|
||||
tunnelProviderManager.isEnabled = true
|
||||
|
||||
if (tunnelConfiguration.activationType == .activateManually) {
|
||||
tunnelProviderManager.onDemandRules = []
|
||||
tunnelProviderManager.isOnDemandEnabled = false
|
||||
} else {
|
||||
tunnelProviderManager.onDemandRules = onDemandRules(for: tunnelConfiguration.activationType)
|
||||
tunnelProviderManager.isOnDemandEnabled = true
|
||||
}
|
||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
||||
|
||||
tunnelProviderManager.saveToPreferences { [weak self] (error) in
|
||||
defer { self?.isAddingTunnel = false }
|
||||
@@ -114,7 +110,8 @@ class TunnelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
func modify(tunnel: TunnelContainer, with tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (TunnelManagementError?) -> Void) {
|
||||
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration,
|
||||
activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelManagementError?) -> Void) {
|
||||
let tunnelName = tunnelConfiguration.interface.name
|
||||
if tunnelName.isEmpty {
|
||||
completionHandler(TunnelManagementError.tunnelAlreadyExistsWithThatName)
|
||||
@@ -138,13 +135,7 @@ class TunnelsManager {
|
||||
tunnelProviderManager.localizedDescription = tunnelName
|
||||
tunnelProviderManager.isEnabled = true
|
||||
|
||||
if (tunnelConfiguration.activationType == .activateManually) {
|
||||
tunnelProviderManager.onDemandRules = []
|
||||
tunnelProviderManager.isOnDemandEnabled = false
|
||||
} else {
|
||||
tunnelProviderManager.onDemandRules = onDemandRules(for: tunnelConfiguration.activationType)
|
||||
tunnelProviderManager.isOnDemandEnabled = true
|
||||
}
|
||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
||||
|
||||
tunnelProviderManager.saveToPreferences { [weak self] (error) in
|
||||
defer { self?.isModifyingTunnel = false }
|
||||
@@ -229,26 +220,6 @@ class TunnelsManager {
|
||||
t.refreshConnectionStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func onDemandRules(for activationType: ActivationType) -> [NEOnDemandRule] {
|
||||
switch (activationType) {
|
||||
case .activateManually: return []
|
||||
case .useOnDemandOverWifiAndCellular:
|
||||
return [NEOnDemandRuleConnect()]
|
||||
case .useOnDemandOverWifiOnly:
|
||||
let connectOnWifiRule = NEOnDemandRuleConnect()
|
||||
connectOnWifiRule.interfaceTypeMatch = .wiFi
|
||||
let disconnectOnCellularRule = NEOnDemandRuleDisconnect()
|
||||
disconnectOnCellularRule.interfaceTypeMatch = .cellular
|
||||
return [connectOnWifiRule, disconnectOnCellularRule]
|
||||
case .useOnDemandOverCellularOnly:
|
||||
let connectOnCellularRule = NEOnDemandRuleConnect()
|
||||
connectOnCellularRule.interfaceTypeMatch = .cellular
|
||||
let disconnectOnWifiRule = NEOnDemandRuleDisconnect()
|
||||
disconnectOnWifiRule.interfaceTypeMatch = .wiFi
|
||||
return [connectOnCellularRule, disconnectOnWifiRule]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TunnelContainer: NSObject {
|
||||
@@ -275,6 +246,10 @@ class TunnelContainer: NSObject {
|
||||
return (tunnelProvider.protocolConfiguration as! NETunnelProviderProtocol).tunnelConfiguration()
|
||||
}
|
||||
|
||||
func activateOnDemandSetting() -> ActivateOnDemandSetting {
|
||||
return ActivateOnDemandSetting(from: tunnelProvider)
|
||||
}
|
||||
|
||||
func refreshConnectionStatus() {
|
||||
let status = TunnelStatus(from: self.tunnelProvider.connection.status)
|
||||
self.status = status
|
||||
|
||||
Reference in New Issue
Block a user