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:
parent
39a067cb96
commit
cc122d7463
|
@ -1,66 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
enum ActivationType {
|
||||
case activateManually
|
||||
case useOnDemandOverWifiAndCellular
|
||||
case useOnDemandOverWifiOnly
|
||||
case useOnDemandOverCellularOnly
|
||||
}
|
||||
|
||||
extension ActivationType: Codable {
|
||||
// We use separate coding keys in case we might have a enum with associated values in the future
|
||||
enum CodingKeys: CodingKey {
|
||||
case activateManually
|
||||
case useOnDemandOverWifiAndCellular
|
||||
case useOnDemandOverWifiOnly
|
||||
case useOnDemandOverCellularOnly
|
||||
}
|
||||
|
||||
// Decoding error
|
||||
enum DecodingError: Error {
|
||||
case invalidInput
|
||||
}
|
||||
|
||||
// Encoding
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .activateManually:
|
||||
try container.encode(true, forKey: CodingKeys.activateManually)
|
||||
case .useOnDemandOverWifiAndCellular:
|
||||
try container.encode(true, forKey: CodingKeys.useOnDemandOverWifiAndCellular)
|
||||
case .useOnDemandOverWifiOnly:
|
||||
try container.encode(true, forKey: CodingKeys.useOnDemandOverWifiOnly)
|
||||
case .useOnDemandOverCellularOnly:
|
||||
try container.encode(true, forKey: CodingKeys.useOnDemandOverCellularOnly)
|
||||
}
|
||||
}
|
||||
|
||||
// Decoding
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let isValid = try? container.decode(Bool.self, forKey: CodingKeys.activateManually), isValid {
|
||||
self = .activateManually
|
||||
return
|
||||
}
|
||||
|
||||
if let isValid = try? container.decode(Bool.self, forKey: CodingKeys.useOnDemandOverWifiAndCellular), isValid {
|
||||
self = .useOnDemandOverWifiAndCellular
|
||||
return
|
||||
}
|
||||
|
||||
if let isValid = try? container.decode(Bool.self, forKey: CodingKeys.useOnDemandOverWifiOnly), isValid {
|
||||
self = .useOnDemandOverWifiOnly
|
||||
return
|
||||
}
|
||||
|
||||
if let isValid = try? container.decode(Bool.self, forKey: CodingKeys.useOnDemandOverCellularOnly), isValid {
|
||||
self = .useOnDemandOverCellularOnly
|
||||
return
|
||||
}
|
||||
|
||||
throw DecodingError.invalidInput
|
||||
}
|
||||
}
|
|
@ -4,14 +4,12 @@
|
|||
import Foundation
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
final class TunnelConfiguration {
|
||||
final class TunnelConfiguration: Codable {
|
||||
var interface: InterfaceConfiguration
|
||||
let peers: [PeerConfiguration]
|
||||
var activationType: ActivationType
|
||||
init(interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
|
||||
self.interface = interface
|
||||
self.peers = peers
|
||||
self.activationType = .activateManually
|
||||
|
||||
let peerPublicKeysArray = peers.map { $0.publicKey }
|
||||
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
||||
|
@ -57,21 +55,3 @@ struct PeerConfiguration: Codable {
|
|||
if (publicKey.count != 32) { fatalError("Invalid public key") }
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfiguration: Encodable { }
|
||||
extension TunnelConfiguration: Decodable {
|
||||
enum CodingKeys: CodingKey {
|
||||
case interface
|
||||
case peers
|
||||
case activationType
|
||||
}
|
||||
convenience init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let interface = try values.decode(InterfaceConfiguration.self, forKey: .interface)
|
||||
let peers = try values.decode([PeerConfiguration].self, forKey: .peers)
|
||||
let activationType = (try? values.decode(ActivationType.self, forKey: .activationType)) ?? .activateManually
|
||||
|
||||
self.init(interface: interface, peers: peers)
|
||||
self.activationType = activationType
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ extension NETunnelProviderProtocol {
|
|||
providerBundleIdentifier = "\(appId).network-extension"
|
||||
providerConfiguration = [
|
||||
"tunnelConfiguration": serializedTunnelConfiguration,
|
||||
"tunnelConfigurationVersion": 2
|
||||
"tunnelConfigurationVersion": 1
|
||||
]
|
||||
|
||||
let endpoints = tunnelConfiguration.peers.compactMap({$0.endpoint})
|
||||
|
|
|
@ -49,8 +49,7 @@
|
|||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
||||
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
||||
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
|
||||
6FFA5DA42197085D0001E2F7 /* ActivationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivationType.swift */; };
|
||||
6FFA5DA521970B370001E2F7 /* ActivationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivationType.swift */; };
|
||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -135,7 +134,7 @@
|
|||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
|
||||
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
||||
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
||||
6FFA5DA32197085D0001E2F7 /* ActivationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivationType.swift; sourceTree = "<group>"; };
|
||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -232,7 +231,6 @@
|
|||
6F7774E9217229DB006A79B3 /* IPAddressRange.swift */,
|
||||
6F693A552179E556008551C1 /* Endpoint.swift */,
|
||||
6F628C3E217F3413003482A3 /* DNSServer.swift */,
|
||||
6FFA5DA32197085D0001E2F7 /* ActivationType.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
|
@ -241,6 +239,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
||||
);
|
||||
path = VPN;
|
||||
sourceTree = "<group>";
|
||||
|
@ -498,7 +497,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6FFA5DA521970B370001E2F7 /* ActivationType.swift in Sources */,
|
||||
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */,
|
||||
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||
6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */,
|
||||
|
@ -540,7 +538,7 @@
|
|||
6FDEF802218646BA00D8FBF6 /* ZipArchive.swift in Sources */,
|
||||
6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
|
||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
||||
6FFA5DA42197085D0001E2F7 /* ActivationType.swift in Sources */,
|
||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue