From cc122d7463f98ab3f26d46ab849173ffd58d7951 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Mon, 12 Nov 2018 14:02:09 +0530 Subject: [PATCH] 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 --- WireGuard/Shared/Model/ActivationType.swift | 66 ---------------- WireGuard/Shared/Model/Configuration.swift | 22 +----- .../NETunnelProviderProtocol+Extension.swift | 2 +- WireGuard/WireGuard.xcodeproj/project.pbxproj | 10 +-- .../VPN/ActivateOnDemandSetting.swift | 75 +++++++++++++++++++ WireGuard/WireGuard/VPN/TunnelsManager.swift | 47 +++--------- 6 files changed, 92 insertions(+), 130 deletions(-) delete mode 100644 WireGuard/Shared/Model/ActivationType.swift create mode 100644 WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift diff --git a/WireGuard/Shared/Model/ActivationType.swift b/WireGuard/Shared/Model/ActivationType.swift deleted file mode 100644 index ea5927d..0000000 --- a/WireGuard/Shared/Model/ActivationType.swift +++ /dev/null @@ -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 - } -} diff --git a/WireGuard/Shared/Model/Configuration.swift b/WireGuard/Shared/Model/Configuration.swift index f6598bb..41ff7bc 100644 --- a/WireGuard/Shared/Model/Configuration.swift +++ b/WireGuard/Shared/Model/Configuration.swift @@ -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(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 - } -} diff --git a/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift b/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift index 591b0eb..ec8b294 100644 --- a/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift +++ b/WireGuard/Shared/NETunnelProviderProtocol+Extension.swift @@ -14,7 +14,7 @@ extension NETunnelProviderProtocol { providerBundleIdentifier = "\(appId).network-extension" providerConfiguration = [ "tunnelConfiguration": serializedTunnelConfiguration, - "tunnelConfigurationVersion": 2 + "tunnelConfigurationVersion": 1 ] let endpoints = tunnelConfiguration.peers.compactMap({$0.endpoint}) diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 5145ce1..508b0f9 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -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 = ""; }; 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = ""; }; 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = ""; }; - 6FFA5DA32197085D0001E2F7 /* ActivationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivationType.swift; sourceTree = ""; }; + 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = ""; }; /* 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 = ""; @@ -241,6 +239,7 @@ isa = PBXGroup; children = ( 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */, + 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */, ); path = VPN; sourceTree = ""; @@ -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; }; diff --git a/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift b/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift new file mode 100644 index 0000000..a2cbe00 --- /dev/null +++ b/WireGuard/WireGuard/VPN/ActivateOnDemandSetting.swift @@ -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) +} diff --git a/WireGuard/WireGuard/VPN/TunnelsManager.swift b/WireGuard/WireGuard/VPN/TunnelsManager.swift index 8eb2112..d75e6c0 100644 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ b/WireGuard/WireGuard/VPN/TunnelsManager.swift @@ -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