Removing a tunnel from iOS's settings is now immediately reflected in app

Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
Eric Kuck
2018-12-21 21:59:43 -06:00
parent 8365adf435
commit 9098cd1161
6 changed files with 72 additions and 52 deletions
+27 -17
View File
@@ -53,6 +53,28 @@ class TunnelsManager {
#endif #endif
} }
func reload(completionHandler: @escaping (Bool) -> Void) {
#if targetEnvironment(simulator)
completionHandler(.success(false))
#else
NETunnelProviderManager.loadAllFromPreferences { managers, _ in
guard let managers = managers else {
completionHandler(false)
return
}
let newTunnels = managers.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
let hasChanges = self.tunnels.map { $0.name } != newTunnels.map { $0.name }
if hasChanges {
self.tunnels = newTunnels
completionHandler(true)
} else {
completionHandler(false)
}
}
#endif
}
func add(tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) { func add(tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
let tunnelName = tunnelConfiguration.name ?? "" let tunnelName = tunnelConfiguration.name ?? ""
if tunnelName.isEmpty { if tunnelName.isEmpty {
@@ -255,8 +277,6 @@ class TunnelsManager {
} }
private func startObservingTunnelStatuses() { private func startObservingTunnelStatuses() {
guard statusObservationToken == nil else { return }
statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
guard let self = self, guard let self = self,
let session = statusChangeNotification.object as? NETunnelProviderSession, let session = statusChangeNotification.object as? NETunnelProviderSession,
@@ -265,14 +285,13 @@ class TunnelsManager {
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'") wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
// Track what happened to our attempt to start the tunnel
if tunnel.isAttemptingActivation { if tunnel.isAttemptingActivation {
if session.status == .connected { if session.status == .connected {
tunnel.isAttemptingActivation = false tunnel.isAttemptingActivation = false
self.activationDelegate?.tunnelActivationSucceeded(tunnel: tunnel) self.activationDelegate?.tunnelActivationSucceeded(tunnel: tunnel)
} else if session.status == .disconnected { } else if session.status == .disconnected {
tunnel.isAttemptingActivation = false tunnel.isAttemptingActivation = false
if let (title, message) = self.lastErrorTextFromNetworkExtension(for: tunnel) { if let (title, message) = lastErrorTextFromNetworkExtension(for: tunnel) {
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailedWithExtensionError(title: title, message: message, wasOnDemandEnabled: tunnelProvider.isOnDemandEnabled)) self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailedWithExtensionError(title: title, message: message, wasOnDemandEnabled: tunnelProvider.isOnDemandEnabled))
} else { } else {
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed(wasOnDemandEnabled: tunnelProvider.isOnDemandEnabled)) self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed(wasOnDemandEnabled: tunnelProvider.isOnDemandEnabled))
@@ -280,9 +299,7 @@ class TunnelsManager {
} }
} }
// In case we're restarting the tunnel
if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) { if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) {
// Don't change tunnel.status when disconnecting for a restart
if session.status == .disconnected { if session.status == .disconnected {
tunnel.startActivation(activationDelegate: self.activationDelegate) tunnel.startActivation(activationDelegate: self.activationDelegate)
} }
@@ -293,11 +310,12 @@ class TunnelsManager {
} }
} }
func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? { }
private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
guard let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL else { return nil } guard let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL else { return nil }
guard let lastErrorData = try? Data(contentsOf: lastErrorFileURL) else { return nil } guard let lastErrorData = try? Data(contentsOf: lastErrorFileURL) else { return nil }
guard let lastErrorText = String(data: lastErrorData, encoding: .utf8) else { return nil } guard let lastErrorStrings = String(data: lastErrorData, encoding: .utf8)?.splitToArray(separator: "\n") else { return nil }
let lastErrorStrings = lastErrorText.splitToArray(separator: "\n")
guard lastErrorStrings.count == 2 && tunnel.activationAttemptId == lastErrorStrings[0] else { return nil } guard lastErrorStrings.count == 2 && tunnel.activationAttemptId == lastErrorStrings[0] else { return nil }
switch PacketTunnelProviderError(rawValue: lastErrorStrings[1]) { switch PacketTunnelProviderError(rawValue: lastErrorStrings[1]) {
@@ -314,14 +332,6 @@ class TunnelsManager {
default: default:
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage")) return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage"))
} }
}
deinit {
if let statusObservationToken = statusObservationToken {
NotificationCenter.default.removeObserver(statusObservationToken)
}
}
} }
class TunnelContainer: NSObject { class TunnelContainer: NSObject {
+2 -5
View File
@@ -10,8 +10,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
var mainVC: MainViewController? var mainVC: MainViewController?
func application(_ application: UIApplication, func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path) Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
let window = UIWindow(frame: UIScreen.main.bounds) let window = UIWindow(frame: UIScreen.main.bounds)
@@ -50,9 +49,7 @@ extension AppDelegate {
return true return true
} }
func application(_ application: UIApplication, func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
viewControllerWithRestorationIdentifierPath identifierComponents: [String],
coder: NSCoder) -> UIViewController? {
guard let vcIdentifier = identifierComponents.last else { return nil } guard let vcIdentifier = identifierComponents.last else { return nil }
if vcIdentifier.hasPrefix("TunnelDetailVC:") { if vcIdentifier.hasPrefix("TunnelDetailVC:") {
let tunnelName = String(vcIdentifier.suffix(vcIdentifier.count - "TunnelDetailVC:".count)) let tunnelName = String(vcIdentifier.suffix(vcIdentifier.count - "TunnelDetailVC:".count))
@@ -7,8 +7,8 @@ class MainViewController: UISplitViewController {
var tunnelsManager: TunnelsManager? var tunnelsManager: TunnelsManager?
var onTunnelsManagerReady: ((TunnelsManager) -> Void)? var onTunnelsManagerReady: ((TunnelsManager) -> Void)?
var tunnelsListVC: TunnelsListTableViewController? var tunnelsListVC: TunnelsListTableViewController?
private var foregroundObservationToken: AnyObject?
init() { init() {
let detailVC = UIViewController() let detailVC = UIViewController()
@@ -57,7 +57,31 @@ class MainViewController: UISplitViewController {
self.onTunnelsManagerReady?(tunnelsManager) self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil self.onTunnelsManagerReady = nil
} }
foregroundObservationToken = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
self.tunnelsManager?.reload { [weak self] hasChanges in
guard let self = self, let tunnelsManager = self.tunnelsManager, hasChanges else { return }
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
if self.isCollapsed {
(self.viewControllers[0] as? UINavigationController)?.popViewController(animated: false)
} else {
let detailVC = UIViewController()
detailVC.view.backgroundColor = .white
let detailNC = UINavigationController(rootViewController: detailVC)
self.showDetailViewController(detailNC, sender: self)
} }
if let presentedNavController = self.presentedViewController as? UINavigationController, presentedNavController.viewControllers.first is TunnelEditTableViewController {
self.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
}
}
} }
extension MainViewController: TunnelsManagerActivationDelegate { extension MainViewController: TunnelsManagerActivationDelegate {
@@ -3,8 +3,6 @@
import UIKit import UIKit
// MARK: TunnelDetailTableViewController
class TunnelDetailTableViewController: UITableViewController { class TunnelDetailTableViewController: UITableViewController {
private enum Section { private enum Section {
@@ -44,11 +42,6 @@ class TunnelDetailTableViewController: UITableViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
onDemandStatusObservationToken = nil
statusObservationToken = nil
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
title = tunnelViewModel.interfaceData[.name] title = tunnelViewModel.interfaceData[.name]
@@ -250,7 +243,7 @@ extension TunnelDetailTableViewController {
return return
} }
} }
if self.splitViewController?.isCollapsed ?? true { if self.splitViewController?.isCollapsed != false {
self.navigationController?.navigationController?.popToRootViewController(animated: true) self.navigationController?.navigationController?.popToRootViewController(animated: true)
} else { } else {
let detailVC = UIViewController() let detailVC = UIViewController()
@@ -13,10 +13,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var lastFirstInterface: NWInterface? private var lastFirstInterface: NWInterface?
private var packetTunnelSettingsGenerator: PacketTunnelSettingsGenerator? private var packetTunnelSettingsGenerator: PacketTunnelSettingsGenerator?
deinit {
networkMonitor?.cancel()
}
override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) { override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
let activationAttemptId = options?["activationAttemptId"] as? String let activationAttemptId = options?["activationAttemptId"] as? String
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)