2018-10-24 01:37:28 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// 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-27 13:00:07 +00:00
|
|
|
enum TunnelsManagerError: Error {
|
2018-10-29 18:08:34 +00:00
|
|
|
case noEndpoint
|
2018-10-27 13:00:07 +00:00
|
|
|
case dnsResolutionFailed
|
|
|
|
case tunnelOperationFailed
|
|
|
|
case attemptingActivationWhenAnotherTunnelIsActive
|
|
|
|
case attemptingActivationWhenTunnelIsNotInactive
|
|
|
|
case attemptingDeactivationWhenTunnelIsInactive
|
|
|
|
}
|
|
|
|
|
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-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]) {
|
|
|
|
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
|
|
|
|
if (tunnel.status != .inactive) {
|
|
|
|
currentTunnel = tunnel
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
self.tunnels = tunnels
|
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-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
|
|
|
|
}
|
|
|
|
|
|
|
|
func add(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (TunnelContainer?, Error?) -> Void) {
|
|
|
|
let tunnelName = tunnelConfiguration.interface.name
|
|
|
|
assert(!tunnelName.isEmpty)
|
|
|
|
|
|
|
|
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 {
|
|
|
|
completionHandler(nil, error)
|
|
|
|
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)
|
|
|
|
s.delegate?.tunnelAdded(at: index)
|
|
|
|
completionHandler(tunnel, nil)
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func modify(tunnel: TunnelContainer, with tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (Error?) -> 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-28 09:25:24 +00:00
|
|
|
if (isNameChanged) {
|
|
|
|
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 }
|
|
|
|
guard (error != nil) else {
|
|
|
|
completionHandler(error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if let s = self {
|
|
|
|
if (isNameChanged) {
|
|
|
|
s.tunnels.remove(at: tunnel.index)
|
|
|
|
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)
|
|
|
|
s.delegate?.tunnelsChanged()
|
|
|
|
} else {
|
|
|
|
s.delegate?.tunnelModified(at: tunnel.index)
|
|
|
|
}
|
|
|
|
completionHandler(nil)
|
|
|
|
}
|
|
|
|
}
|
2018-10-15 08:05:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func remove(tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
|
2018-10-25 10:20:27 +00:00
|
|
|
let tunnelProviderManager = tunnel.tunnelProvider
|
|
|
|
let tunnelIndex = tunnel.index
|
|
|
|
|
|
|
|
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-10-25 10:20:27 +00:00
|
|
|
completionHandler(error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if let s = self {
|
|
|
|
for i in ((tunnelIndex + 1) ..< s.tunnels.count) {
|
|
|
|
s.tunnels[i].index = s.tunnels[i].index + 1
|
|
|
|
}
|
|
|
|
s.tunnels.remove(at: tunnelIndex)
|
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-27 13:00:07 +00:00
|
|
|
completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive)
|
2018-10-26 09:25:20 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-27 13:00:07 +00:00
|
|
|
guard (currentTunnel == nil) else {
|
|
|
|
completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsActive)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
tunnel.startActivation(completionHandler: completionHandler)
|
2018-10-28 12:39:38 +00:00
|
|
|
setCurrentTunnel(tunnel: tunnel)
|
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) {
|
|
|
|
completionHandler(TunnelsManagerError.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-10-26 09:25:20 +00:00
|
|
|
assert(status == .inactive)
|
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-26 13:39:11 +00:00
|
|
|
let dnsResolver = DNSResolver(endpoints: endpoints)
|
|
|
|
assert(self.dnsResolver == nil)
|
2018-10-29 00:30:31 +00:00
|
|
|
if let endpoints = dnsResolver.resolveWithoutNetworkRequests() {
|
2018-10-29 18:08:34 +00:00
|
|
|
guard (endpoints.contains(where: { $0 != nil })) else {
|
|
|
|
completionHandler(TunnelsManagerError.noEndpoint)
|
2018-10-29 18:54:50 +00:00
|
|
|
status = .inactive
|
2018-10-29 18:08:34 +00:00
|
|
|
return
|
|
|
|
}
|
2018-10-29 00:30:31 +00:00
|
|
|
self.tunnelProvider.loadFromPreferences { [weak self] (error) in
|
|
|
|
guard let s = self else { return }
|
2018-10-28 23:32:57 +00:00
|
|
|
s.startObservingTunnelStatus()
|
|
|
|
let session = (s.tunnelProvider.connection as! NETunnelProviderSession)
|
|
|
|
do {
|
|
|
|
let tunnelOptions = PacketTunnelOptionsGenerator.generateOptions(
|
|
|
|
from: tunnelConfiguration, withResolvedEndpoints: endpoints)
|
|
|
|
try session.startTunnel(options: tunnelOptions)
|
|
|
|
} catch (let error) {
|
|
|
|
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)")
|
|
|
|
completionHandler(error)
|
2018-10-29 18:54:50 +00:00
|
|
|
s.status = .inactive
|
|
|
|
return
|
2018-10-28 23:32:57 +00:00
|
|
|
}
|
2018-10-29 18:54:50 +00:00
|
|
|
completionHandler(nil)
|
2018-10-26 13:39:11 +00:00
|
|
|
}
|
2018-10-29 00:30:31 +00:00
|
|
|
} else {
|
|
|
|
self.dnsResolver = dnsResolver
|
|
|
|
status = .resolvingEndpointDomains
|
|
|
|
dnsResolver.resolve { [weak self] endpoints in
|
|
|
|
guard let s = self else { return }
|
|
|
|
assert(s.status == .resolvingEndpointDomains)
|
|
|
|
s.dnsResolver = nil
|
|
|
|
guard let endpoints = endpoints else {
|
|
|
|
completionHandler(TunnelsManagerError.dnsResolutionFailed)
|
|
|
|
s.status = .inactive
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.tunnelProvider.loadFromPreferences { [weak s] (error) in
|
|
|
|
guard let s = s else { return }
|
|
|
|
s.startObservingTunnelStatus()
|
|
|
|
let session = (s.tunnelProvider.connection as! NETunnelProviderSession)
|
|
|
|
do {
|
|
|
|
let tunnelOptions = PacketTunnelOptionsGenerator.generateOptions(
|
|
|
|
from: tunnelConfiguration, withResolvedEndpoints: endpoints)
|
|
|
|
try session.startTunnel(options: tunnelOptions)
|
|
|
|
} catch (let error) {
|
|
|
|
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)")
|
2018-10-29 18:54:50 +00:00
|
|
|
s.status = .inactive
|
|
|
|
return
|
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
|
|
|
}
|
|
|
|
|
|
|
|
private func startObservingTunnelStatus() {
|
|
|
|
let connection = tunnelProvider.connection
|
|
|
|
statusObservationToken = NotificationCenter.default.addObserver(
|
|
|
|
forName: .NEVPNStatusDidChange,
|
|
|
|
object: connection,
|
|
|
|
queue: nil) { [weak self] (_) in
|
|
|
|
let status = TunnelStatus(from: connection.status)
|
|
|
|
if let s = self {
|
|
|
|
s.status = status
|
2018-10-27 13:00:07 +00:00
|
|
|
if (status == .inactive) {
|
2018-10-26 09:25:20 +00:00
|
|
|
s.stopObservingTunnelStatus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
case reasserting // On editing an active tunnel, the tunnel shall deactive and then activate
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|