2018-10-24 01:37:28 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2018-10-30 02:57:35 +00:00
|
|
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
2018-10-15 08:05:24 +00:00
|
|
|
|
|
|
|
import Foundation
|
2018-10-25 10:20:27 +00:00
|
|
|
import NetworkExtension
|
|
|
|
import os.log
|
2018-10-15 08:05:24 +00:00
|
|
|
|
2018-10-23 12:11:37 +00:00
|
|
|
protocol TunnelsManagerDelegate: class {
|
2018-10-25 10:20:27 +00:00
|
|
|
func tunnelAdded(at: Int)
|
|
|
|
func tunnelModified(at: Int)
|
|
|
|
func tunnelsChanged()
|
2018-10-28 23:24:57 +00:00
|
|
|
func tunnelRemoved(at: Int)
|
2018-10-23 12:11:37 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 18:07:46 +00:00
|
|
|
enum TunnelActivationError: Error {
|
2018-10-29 18:08:34 +00:00
|
|
|
case noEndpoint
|
2018-10-27 13:00:07 +00:00
|
|
|
case dnsResolutionFailed
|
2018-11-01 11:27:40 +00:00
|
|
|
case tunnelActivationFailed
|
|
|
|
case attemptingActivationWhenAnotherTunnelIsBusy(otherTunnelStatus: TunnelStatus)
|
2018-10-27 13:00:07 +00:00
|
|
|
case attemptingActivationWhenTunnelIsNotInactive
|
|
|
|
case attemptingDeactivationWhenTunnelIsInactive
|
|
|
|
}
|
|
|
|
|
2018-11-01 06:12:32 +00:00
|
|
|
enum TunnelManagementError: Error {
|
|
|
|
case tunnelAlreadyExistsWithThatName
|
|
|
|
case vpnSystemErrorOnAddTunnel
|
|
|
|
case vpnSystemErrorOnModifyTunnel
|
|
|
|
case vpnSystemErrorOnRemoveTunnel
|
|
|
|
}
|
|
|
|
|
2018-10-15 08:05:24 +00:00
|
|
|
class TunnelsManager {
|
|
|
|
|
|
|
|
var tunnels: [TunnelContainer]
|
2018-10-23 12:11:37 +00:00
|
|
|
weak var delegate: TunnelsManagerDelegate? = nil
|
2018-10-15 08:05:24 +00:00
|
|
|
|
2018-10-25 10:20:27 +00:00
|
|
|
private var isAddingTunnel: Bool = false
|
|
|
|
private var isModifyingTunnel: Bool = false
|
|
|
|
private var isDeletingTunnel: Bool = false
|
|
|
|
|
2018-10-31 11:33:32 +00:00
|
|
|
private var tunnelNames: Set<String>
|
2018-10-27 13:00:07 +00:00
|
|
|
private var currentTunnel: TunnelContainer?
|
|
|
|
private var currentTunnelStatusObservationToken: AnyObject?
|
2018-10-15 08:05:24 +00:00
|
|
|
|
2018-10-25 10:20:27 +00:00
|
|
|
init(tunnelProviders: [NETunnelProviderManager]) {
|
2018-10-31 11:33:32 +00:00
|
|
|
var tunnelNames: Set<String> = []
|
2018-10-25 10:20:27 +00:00
|
|
|
var tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0, index: 0) }
|
|
|
|
tunnels.sort { $0.name < $1.name }
|
2018-10-28 12:39:38 +00:00
|
|
|
var currentTunnel: TunnelContainer? = nil
|
2018-10-25 10:20:27 +00:00
|
|
|
for i in 0 ..< tunnels.count {
|
2018-10-28 12:39:38 +00:00
|
|
|
let tunnel = tunnels[i]
|
|
|
|
tunnel.index = i
|
2018-10-31 11:33:32 +00:00
|
|
|
tunnelNames.insert(tunnel.name)
|
2018-10-28 12:39:38 +00:00
|
|
|
if (tunnel.status != .inactive) {
|
|
|
|
currentTunnel = tunnel
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
self.tunnels = tunnels
|
2018-10-31 11:33:32 +00:00
|
|
|
self.tunnelNames = tunnelNames
|
2018-10-28 12:39:38 +00:00
|
|
|
if let currentTunnel = currentTunnel {
|
|
|
|
setCurrentTunnel(tunnel: currentTunnel)
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
|
2018-10-25 10:20:27 +00:00
|
|
|
static func create(completionHandler: @escaping (TunnelsManager?) -> Void) {
|
|
|
|
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
|
|
|
|
if let error = error {
|
2018-10-26 09:25:20 +00:00
|
|
|
os_log("Failed to load tunnel provider managers: %{public}@", log: OSLog.default, type: .debug, "\(error)")
|
2018-10-25 10:20:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
completionHandler(TunnelsManager(tunnelProviders: managers ?? []))
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 11:33:32 +00:00
|
|
|
func containsTunnel(named name: String) -> Bool {
|
|
|
|
return tunnelNames.contains(name)
|
|
|
|
}
|
|
|
|
|
2018-10-25 10:20:27 +00:00
|
|
|
private func insertionIndexFor(tunnelName: String) -> Int {
|
|
|
|
// Wishlist: Use binary search instead
|
|
|
|
for i in 0 ..< tunnels.count {
|
|
|
|
if (tunnelName.lexicographicallyPrecedes(tunnels[i].name)) { return i }
|
|
|
|
}
|
|
|
|
return tunnels.count
|
|
|
|
}
|
|
|
|
|
2018-11-01 06:12:32 +00:00
|
|
|
func add(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (TunnelContainer?, TunnelManagementError?) -> Void) {
|
2018-10-25 10:20:27 +00:00
|
|
|
let tunnelName = tunnelConfiguration.interface.name
|
|
|
|
assert(!tunnelName.isEmpty)
|
2018-11-01 06:12:32 +00:00
|
|
|
|
|
|
|
guard (!containsTunnel(named: tunnelName)) else {
|
|
|
|
completionHandler(nil, TunnelManagementError.tunnelAlreadyExistsWithThatName)
|
|
|
|
return
|
|
|
|
}
|
2018-10-25 10:20:27 +00:00
|
|
|
|
|
|
|
isAddingTunnel = true
|
|
|
|
let tunnelProviderManager = NETunnelProviderManager()
|
|
|
|
tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
|
|
|
|
tunnelProviderManager.localizedDescription = tunnelName
|
|
|
|
tunnelProviderManager.isEnabled = true
|
|
|
|
|
|
|
|
tunnelProviderManager.saveToPreferences { [weak self] (error) in
|
|
|
|
defer { self?.isAddingTunnel = false }
|
|
|
|
guard (error == nil) else {
|
2018-11-01 06:12:32 +00:00
|
|
|
os_log("Add: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)")
|
|
|
|
completionHandler(nil, TunnelManagementError.vpnSystemErrorOnAddTunnel)
|
2018-10-25 10:20:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if let s = self {
|
|
|
|
let index = s.insertionIndexFor(tunnelName: tunnelName)
|
|
|
|
let tunnel = TunnelContainer(tunnel: tunnelProviderManager, index: index)
|
|
|
|
for i in index ..< s.tunnels.count {
|
|
|
|
s.tunnels[i].index = s.tunnels[i].index + 1
|
|
|
|
}
|
|
|
|
s.tunnels.insert(tunnel, at: index)
|
2018-10-31 11:33:32 +00:00
|
|
|
s.tunnelNames.insert(tunnel.name)
|
2018-10-25 10:20:27 +00:00
|
|
|
s.delegate?.tunnelAdded(at: index)
|
|
|
|
completionHandler(tunnel, nil)
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-03 01:51:32 +00:00
|
|
|
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) {
|
|
|
|
addMultiple(tunnelConfigurations: tunnelConfigurations[0...], numberSuccessful: 0, completionHandler: completionHandler)
|
2018-10-31 08:59:54 +00:00
|
|
|
}
|
|
|
|
|
2018-11-03 01:51:32 +00:00
|
|
|
private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, completionHandler: @escaping (UInt) -> Void) {
|
|
|
|
if tunnelConfigurations.isEmpty {
|
|
|
|
completionHandler(numberSuccessful)
|
|
|
|
return
|
|
|
|
}
|
2018-10-31 08:59:54 +00:00
|
|
|
let head = tunnelConfigurations.first!
|
2018-11-03 01:51:32 +00:00
|
|
|
let tail = tunnelConfigurations[1...]
|
2018-10-31 08:59:54 +00:00
|
|
|
self.add(tunnelConfiguration: head) { [weak self] (tunnel, error) in
|
2018-11-03 01:51:32 +00:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (error == nil ? 1 : 0), completionHandler: completionHandler)
|
2018-10-31 08:59:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-01 06:12:32 +00:00
|
|
|
func modify(tunnel: TunnelContainer, with tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (TunnelManagementError?) -> Void) {
|
2018-10-25 10:20:27 +00:00
|
|
|
let tunnelName = tunnelConfiguration.interface.name
|
|
|
|
assert(!tunnelName.isEmpty)
|
|
|
|
|
|
|
|
isModifyingTunnel = true
|
|
|
|
|
|
|
|
let tunnelProviderManager = tunnel.tunnelProvider
|
|
|
|
let isNameChanged = (tunnelName != tunnelProviderManager.localizedDescription)
|
2018-10-31 11:33:32 +00:00
|
|
|
var oldName: String? = nil
|
2018-10-28 09:25:24 +00:00
|
|
|
if (isNameChanged) {
|
2018-11-01 06:12:32 +00:00
|
|
|
guard (!containsTunnel(named: tunnelName)) else {
|
|
|
|
completionHandler(TunnelManagementError.tunnelAlreadyExistsWithThatName)
|
|
|
|
return
|
|
|
|
}
|
2018-10-31 11:33:32 +00:00
|
|
|
oldName = tunnel.name
|
2018-10-28 09:25:24 +00:00
|
|
|
tunnel.name = tunnelName
|
|
|
|
}
|
2018-10-25 10:20:27 +00:00
|
|
|
tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
|
|
|
|
tunnelProviderManager.localizedDescription = tunnelName
|
|
|
|
tunnelProviderManager.isEnabled = true
|
|
|
|
|
|
|
|
tunnelProviderManager.saveToPreferences { [weak self] (error) in
|
|
|
|
defer { self?.isModifyingTunnel = false }
|
2018-11-01 06:12:32 +00:00
|
|
|
guard (error == nil) else {
|
|
|
|
os_log("Modify: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)")
|
|
|
|
completionHandler(TunnelManagementError.vpnSystemErrorOnModifyTunnel)
|
2018-10-25 10:20:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if let s = self {
|
|
|
|
if (isNameChanged) {
|
|
|
|
s.tunnels.remove(at: tunnel.index)
|
2018-10-31 11:33:32 +00:00
|
|
|
s.tunnelNames.remove(oldName!)
|
2018-10-25 10:20:27 +00:00
|
|
|
for i in tunnel.index ..< s.tunnels.count {
|
|
|
|
s.tunnels[i].index = s.tunnels[i].index - 1
|
|
|
|
}
|
|
|
|
let index = s.insertionIndexFor(tunnelName: tunnelName)
|
|
|
|
tunnel.index = index
|
|
|
|
for i in index ..< s.tunnels.count {
|
|
|
|
s.tunnels[i].index = s.tunnels[i].index + 1
|
|
|
|
}
|
|
|
|
s.tunnels.insert(tunnel, at: index)
|
2018-10-31 11:33:32 +00:00
|
|
|
s.tunnelNames.insert(tunnel.name)
|
2018-10-25 10:20:27 +00:00
|
|
|
s.delegate?.tunnelsChanged()
|
|
|
|
} else {
|
|
|
|
s.delegate?.tunnelModified(at: tunnel.index)
|
|
|
|
}
|
2018-11-01 10:06:59 +00:00
|
|
|
|
|
|
|
if (tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting) {
|
|
|
|
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
|
|
|
tunnel.beginRestart()
|
|
|
|
}
|
|
|
|
|
2018-10-25 10:20:27 +00:00
|
|
|
completionHandler(nil)
|
|
|
|
}
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
|
2018-11-01 06:12:32 +00:00
|
|
|
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelManagementError?) -> Void) {
|
2018-10-25 10:20:27 +00:00
|
|
|
let tunnelProviderManager = tunnel.tunnelProvider
|
|
|
|
let tunnelIndex = tunnel.index
|
2018-10-31 11:33:32 +00:00
|
|
|
let tunnelName = tunnel.name
|
2018-10-25 10:20:27 +00:00
|
|
|
|
|
|
|
isDeletingTunnel = true
|
|
|
|
|
|
|
|
tunnelProviderManager.removeFromPreferences { [weak self] (error) in
|
|
|
|
defer { self?.isDeletingTunnel = false }
|
2018-10-28 23:24:57 +00:00
|
|
|
guard (error == nil) else {
|
2018-11-01 06:12:32 +00:00
|
|
|
os_log("Remove: Saving configuration failed: %{public}@", log: OSLog.default, type: .error, "\(error!)")
|
|
|
|
completionHandler(TunnelManagementError.vpnSystemErrorOnRemoveTunnel)
|
2018-10-25 10:20:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if let s = self {
|
|
|
|
for i in ((tunnelIndex + 1) ..< s.tunnels.count) {
|
2018-11-01 10:23:10 +00:00
|
|
|
s.tunnels[i].index = s.tunnels[i].index - 1
|
2018-10-25 10:20:27 +00:00
|
|
|
}
|
|
|
|
s.tunnels.remove(at: tunnelIndex)
|
2018-10-31 11:33:32 +00:00
|
|
|
s.tunnelNames.remove(tunnelName)
|
2018-10-28 23:24:57 +00:00
|
|
|
s.delegate?.tunnelRemoved(at: tunnelIndex)
|
2018-10-25 10:20:27 +00:00
|
|
|
}
|
|
|
|
completionHandler(nil)
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func numberOfTunnels() -> Int {
|
|
|
|
return tunnels.count
|
|
|
|
}
|
|
|
|
|
|
|
|
func tunnel(at index: Int) -> TunnelContainer {
|
|
|
|
return tunnels[index]
|
|
|
|
}
|
2018-10-26 09:25:20 +00:00
|
|
|
|
2018-10-27 13:00:07 +00:00
|
|
|
func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
|
2018-10-26 09:25:20 +00:00
|
|
|
guard (tunnel.status == .inactive) else {
|
2018-10-31 18:07:46 +00:00
|
|
|
completionHandler(TunnelActivationError.attemptingActivationWhenTunnelIsNotInactive)
|
2018-10-26 09:25:20 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-27 13:00:07 +00:00
|
|
|
guard (currentTunnel == nil) else {
|
2018-11-01 11:27:40 +00:00
|
|
|
completionHandler(TunnelActivationError.attemptingActivationWhenAnotherTunnelIsBusy(otherTunnelStatus: currentTunnel!.status))
|
2018-10-27 13:00:07 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-28 12:39:38 +00:00
|
|
|
setCurrentTunnel(tunnel: tunnel)
|
2018-10-31 20:24:19 +00:00
|
|
|
tunnel.startActivation(completionHandler: completionHandler)
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
|
|
|
|
2018-10-27 13:00:07 +00:00
|
|
|
func startDeactivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
|
|
|
|
if (tunnel.status == .inactive) {
|
2018-10-31 18:07:46 +00:00
|
|
|
completionHandler(TunnelActivationError.attemptingDeactivationWhenTunnelIsInactive)
|
2018-10-26 09:25:20 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-27 13:00:07 +00:00
|
|
|
assert(tunnel.index == currentTunnel!.index)
|
|
|
|
|
|
|
|
tunnel.startDeactivation()
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
2018-10-28 12:39:38 +00:00
|
|
|
|
|
|
|
private func setCurrentTunnel(tunnel: TunnelContainer) {
|
|
|
|
currentTunnel = tunnel
|
|
|
|
currentTunnelStatusObservationToken = tunnel.observe(\.status) { [weak self] (tunnel, change) in
|
|
|
|
guard let s = self else { return }
|
|
|
|
if (tunnel.status == .inactive) {
|
|
|
|
s.currentTunnel = nil
|
|
|
|
s.currentTunnelStatusObservationToken = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
2018-10-25 10:20:27 +00:00
|
|
|
|
|
|
|
extension NETunnelProviderProtocol {
|
|
|
|
convenience init?(tunnelConfiguration: TunnelConfiguration) {
|
|
|
|
assert(!tunnelConfiguration.interface.name.isEmpty)
|
|
|
|
guard let serializedTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
|
|
|
|
|
|
|
|
self.init()
|
|
|
|
|
|
|
|
let appId = Bundle.main.bundleIdentifier!
|
|
|
|
let firstValidEndpoint = tunnelConfiguration.peers.first(where: { $0.endpoint != nil })?.endpoint
|
|
|
|
|
|
|
|
providerBundleIdentifier = "\(appId).WireGuardNetworkExtension"
|
|
|
|
providerConfiguration = [
|
2018-10-25 12:03:42 +00:00
|
|
|
"tunnelConfiguration": serializedTunnelConfiguration,
|
|
|
|
"tunnelConfigurationVersion": 1
|
2018-10-25 10:20:27 +00:00
|
|
|
]
|
|
|
|
serverAddress = firstValidEndpoint?.stringRepresentation() ?? "Unspecified"
|
|
|
|
username = tunnelConfiguration.interface.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func tunnelConfiguration() -> TunnelConfiguration? {
|
|
|
|
guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return nil }
|
|
|
|
return try? JSONDecoder().decode(TunnelConfiguration.self, from: serializedTunnelConfiguration)
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 09:25:20 +00:00
|
|
|
|
|
|
|
class TunnelContainer: NSObject {
|
|
|
|
@objc dynamic var name: String
|
|
|
|
@objc dynamic var status: TunnelStatus
|
|
|
|
|
|
|
|
fileprivate let tunnelProvider: NETunnelProviderManager
|
|
|
|
fileprivate var index: Int
|
|
|
|
fileprivate var statusObservationToken: AnyObject?
|
|
|
|
|
2018-10-26 13:39:11 +00:00
|
|
|
private var dnsResolver: DNSResolver? = nil
|
|
|
|
|
2018-10-26 09:25:20 +00:00
|
|
|
init(tunnel: NETunnelProviderManager, index: Int) {
|
|
|
|
self.name = tunnel.localizedDescription ?? "Unnamed"
|
|
|
|
let status = TunnelStatus(from: tunnel.connection.status)
|
|
|
|
self.status = status
|
|
|
|
self.tunnelProvider = tunnel
|
|
|
|
self.index = index
|
|
|
|
super.init()
|
|
|
|
if (status != .inactive) {
|
|
|
|
startObservingTunnelStatus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tunnelConfiguration() -> TunnelConfiguration? {
|
|
|
|
return (tunnelProvider.protocolConfiguration as! NETunnelProviderProtocol).tunnelConfiguration()
|
|
|
|
}
|
|
|
|
|
2018-10-27 13:00:07 +00:00
|
|
|
fileprivate func startActivation(completionHandler: @escaping (Error?) -> Void) {
|
2018-11-01 10:06:59 +00:00
|
|
|
assert(status == .inactive || status == .restarting)
|
2018-10-30 11:04:46 +00:00
|
|
|
assert(self.dnsResolver == nil)
|
|
|
|
|
2018-10-26 13:39:11 +00:00
|
|
|
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
|
2018-10-26 23:11:05 +00:00
|
|
|
let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
|
2018-10-30 11:04:46 +00:00
|
|
|
|
|
|
|
// Ensure there's a tunner server address we can give to iOS
|
|
|
|
guard (endpoints.contains(where: { $0 != nil })) else {
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
|
|
self?.status = .inactive
|
2018-10-31 18:07:46 +00:00
|
|
|
completionHandler(TunnelActivationError.noEndpoint)
|
2018-10-26 13:39:11 +00:00
|
|
|
}
|
2018-10-30 11:04:46 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve DNS and start the tunnel
|
|
|
|
let dnsResolver = DNSResolver(endpoints: endpoints)
|
|
|
|
let resolvedEndpoints = dnsResolver.resolveWithoutNetworkRequests()
|
|
|
|
if let resolvedEndpoints = resolvedEndpoints {
|
|
|
|
// If we don't have to make a DNS network request, we never
|
|
|
|
// change the status to .resolvingEndpointDomains
|
|
|
|
startActivation(tunnelConfiguration: tunnelConfiguration,
|
|
|
|
resolvedEndpoints: resolvedEndpoints,
|
|
|
|
completionHandler: completionHandler)
|
2018-10-29 00:30:31 +00:00
|
|
|
} else {
|
|
|
|
status = .resolvingEndpointDomains
|
2018-10-30 11:04:46 +00:00
|
|
|
self.dnsResolver = dnsResolver
|
|
|
|
dnsResolver.resolve { [weak self] resolvedEndpoints in
|
2018-10-29 00:30:31 +00:00
|
|
|
guard let s = self else { return }
|
|
|
|
assert(s.status == .resolvingEndpointDomains)
|
|
|
|
s.dnsResolver = nil
|
2018-10-30 11:04:46 +00:00
|
|
|
guard let resolvedEndpoints = resolvedEndpoints else {
|
2018-10-29 00:30:31 +00:00
|
|
|
s.status = .inactive
|
2018-10-31 18:07:46 +00:00
|
|
|
completionHandler(TunnelActivationError.dnsResolutionFailed)
|
2018-10-29 00:30:31 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-30 11:04:46 +00:00
|
|
|
s.startActivation(tunnelConfiguration: tunnelConfiguration,
|
|
|
|
resolvedEndpoints: resolvedEndpoints,
|
|
|
|
completionHandler: completionHandler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-31 14:58:03 +00:00
|
|
|
fileprivate func startActivation(recursionCount: UInt = 0,
|
|
|
|
lastError: Error? = nil,
|
|
|
|
tunnelConfiguration: TunnelConfiguration,
|
2018-10-30 11:04:46 +00:00
|
|
|
resolvedEndpoints: [Endpoint?],
|
|
|
|
completionHandler: @escaping (Error?) -> Void) {
|
2018-10-31 14:58:03 +00:00
|
|
|
if (recursionCount >= 8) {
|
2018-11-03 01:55:19 +00:00
|
|
|
os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)")
|
2018-11-01 11:27:40 +00:00
|
|
|
completionHandler(TunnelActivationError.tunnelActivationFailed)
|
2018-10-31 14:58:03 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-30 11:04:46 +00:00
|
|
|
// resolvedEndpoints should contain only IP addresses, not any named endpoints
|
|
|
|
assert(resolvedEndpoints.allSatisfy { (resolvedEndpoint) in
|
|
|
|
guard let resolvedEndpoint = resolvedEndpoint else { return true }
|
|
|
|
switch (resolvedEndpoint.host) {
|
|
|
|
case .ipv4(_): return true
|
|
|
|
case .ipv6(_): return true
|
|
|
|
case .name(_, _): return false
|
|
|
|
}
|
|
|
|
})
|
2018-10-31 11:12:29 +00:00
|
|
|
|
|
|
|
os_log("startActivation: Entering", log: OSLog.default, type: .debug)
|
|
|
|
|
|
|
|
guard (tunnelProvider.isEnabled) else {
|
|
|
|
// In case the tunnel had gotten disabled, re-enable and save it,
|
|
|
|
// then call this function again.
|
2018-11-03 01:55:19 +00:00
|
|
|
os_log("startActivation: Tunnel is disabled. Re-enabling and saving", log: OSLog.default, type: .info)
|
2018-10-31 11:12:29 +00:00
|
|
|
tunnelProvider.isEnabled = true
|
|
|
|
tunnelProvider.saveToPreferences { [weak self] (error) in
|
|
|
|
if (error != nil) {
|
|
|
|
os_log("Error saving tunnel after re-enabling: %{public}@", log: OSLog.default, type: .error, "\(error!)")
|
|
|
|
completionHandler(error)
|
|
|
|
return
|
|
|
|
}
|
2018-11-03 01:55:19 +00:00
|
|
|
os_log("startActivation: Tunnel saved after re-enabling", log: OSLog.default, type: .info)
|
2018-10-31 11:12:29 +00:00
|
|
|
os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug)
|
2018-10-31 14:58:03 +00:00
|
|
|
self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown), tunnelConfiguration: tunnelConfiguration, resolvedEndpoints: resolvedEndpoints, completionHandler: completionHandler)
|
2018-10-31 11:12:29 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-30 11:04:46 +00:00
|
|
|
// Start the tunnel
|
2018-10-31 11:12:29 +00:00
|
|
|
startObservingTunnelStatus()
|
|
|
|
let session = (tunnelProvider.connection as! NETunnelProviderSession)
|
|
|
|
do {
|
|
|
|
os_log("startActivation: Generating options", log: OSLog.default, type: .debug)
|
|
|
|
let tunnelOptions = PacketTunnelOptionsGenerator.generateOptions(
|
|
|
|
from: tunnelConfiguration, withResolvedEndpoints: resolvedEndpoints)
|
|
|
|
os_log("startActivation: Starting tunnel", log: OSLog.default, type: .debug)
|
|
|
|
try session.startTunnel(options: tunnelOptions)
|
|
|
|
os_log("startActivation: Success", log: OSLog.default, type: .debug)
|
|
|
|
completionHandler(nil)
|
|
|
|
} catch (let error) {
|
2018-11-03 01:55:19 +00:00
|
|
|
os_log("startActivation: Error starting tunnel. Examining error", log: OSLog.default, type: .debug)
|
2018-10-31 11:12:29 +00:00
|
|
|
guard let vpnError = error as? NEVPNError else {
|
2018-10-30 11:04:46 +00:00
|
|
|
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)")
|
2018-10-31 11:12:29 +00:00
|
|
|
status = .inactive
|
2018-10-30 11:04:46 +00:00
|
|
|
completionHandler(error)
|
2018-10-31 11:12:29 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
guard (vpnError.code == NEVPNError.configurationInvalid || vpnError.code == NEVPNError.configurationStale) else {
|
|
|
|
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)")
|
|
|
|
status = .inactive
|
|
|
|
completionHandler(error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
assert(vpnError.code == NEVPNError.configurationInvalid || vpnError.code == NEVPNError.configurationStale)
|
|
|
|
os_log("startActivation: Error says: %{public}@", log: OSLog.default, type: .debug,
|
|
|
|
vpnError.code == NEVPNError.configurationInvalid ? "Configuration invalid" : "Configuration stale")
|
|
|
|
os_log("startActivation: Will reload tunnel and then try to start it. ", log: OSLog.default, type: .info)
|
|
|
|
tunnelProvider.loadFromPreferences { [weak self] (error) in
|
|
|
|
if (error != nil) {
|
|
|
|
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error!)")
|
|
|
|
self?.status = .inactive
|
|
|
|
completionHandler(error)
|
|
|
|
return
|
|
|
|
}
|
2018-11-03 01:55:19 +00:00
|
|
|
os_log("startActivation: Tunnel reloaded", log: OSLog.default, type: .info)
|
2018-10-31 11:12:29 +00:00
|
|
|
os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug)
|
2018-10-31 14:58:03 +00:00
|
|
|
self?.startActivation(recursionCount: recursionCount + 1, lastError: vpnError, tunnelConfiguration: tunnelConfiguration, resolvedEndpoints: resolvedEndpoints, completionHandler: completionHandler)
|
2018-10-29 00:30:31 +00:00
|
|
|
}
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-27 13:00:07 +00:00
|
|
|
fileprivate func startDeactivation() {
|
2018-10-28 12:16:18 +00:00
|
|
|
assert(status == .active)
|
|
|
|
assert(statusObservationToken != nil)
|
|
|
|
let session = (tunnelProvider.connection as! NETunnelProviderSession)
|
|
|
|
session.stopTunnel()
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
|
|
|
|
2018-11-01 10:06:59 +00:00
|
|
|
fileprivate func beginRestart() {
|
|
|
|
assert(status == .active || status == .activating || status == .reasserting)
|
|
|
|
assert(statusObservationToken != nil)
|
|
|
|
status = .restarting
|
|
|
|
let session = (tunnelProvider.connection as! NETunnelProviderSession)
|
|
|
|
session.stopTunnel()
|
|
|
|
}
|
|
|
|
|
2018-10-26 09:25:20 +00:00
|
|
|
private func startObservingTunnelStatus() {
|
2018-10-31 11:12:29 +00:00
|
|
|
if (statusObservationToken != nil) { return }
|
2018-10-26 09:25:20 +00:00
|
|
|
let connection = tunnelProvider.connection
|
|
|
|
statusObservationToken = NotificationCenter.default.addObserver(
|
|
|
|
forName: .NEVPNStatusDidChange,
|
|
|
|
object: connection,
|
|
|
|
queue: nil) { [weak self] (_) in
|
2018-11-01 10:06:59 +00:00
|
|
|
guard let s = self else { return }
|
|
|
|
if ((s.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) {
|
|
|
|
// Don't change s.status when disconnecting for a restart
|
|
|
|
if (connection.status == .disconnected) {
|
|
|
|
self?.startActivation(completionHandler: { _ in })
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
2018-11-01 10:06:59 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
s.status = TunnelStatus(from: connection.status)
|
|
|
|
if (s.status == .inactive) {
|
|
|
|
s.stopObservingTunnelStatus()
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func stopObservingTunnelStatus() {
|
2018-10-27 13:00:07 +00:00
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
|
|
self?.statusObservationToken = nil
|
|
|
|
}
|
2018-10-26 09:25:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc enum TunnelStatus: Int {
|
|
|
|
case inactive
|
|
|
|
case activating
|
|
|
|
case active
|
|
|
|
case deactivating
|
2018-11-01 10:06:59 +00:00
|
|
|
case reasserting // Not a possible state at present
|
2018-10-26 09:25:20 +00:00
|
|
|
|
2018-11-01 10:06:59 +00:00
|
|
|
case restarting // Restarting tunnel (done after saving modifications to an active tunnel)
|
2018-10-26 09:25:20 +00:00
|
|
|
case resolvingEndpointDomains // DNS resolution in progress
|
|
|
|
|
|
|
|
init(from vpnStatus: NEVPNStatus) {
|
|
|
|
switch (vpnStatus) {
|
|
|
|
case .connected:
|
|
|
|
self = .active
|
|
|
|
case .connecting:
|
|
|
|
self = .activating
|
|
|
|
case .disconnected:
|
|
|
|
self = .inactive
|
|
|
|
case .disconnecting:
|
|
|
|
self = .deactivating
|
|
|
|
case .reasserting:
|
|
|
|
self = .reasserting
|
|
|
|
case .invalid:
|
|
|
|
self = .inactive
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|