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:
@@ -52,6 +52,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 ?? ""
|
||||||
@@ -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,35 +310,28 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
|
}
|
||||||
guard let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL else { return nil }
|
|
||||||
guard let lastErrorData = try? Data(contentsOf: lastErrorFileURL) else { return nil }
|
|
||||||
guard let lastErrorText = String(data: lastErrorData, encoding: .utf8) else { return nil }
|
|
||||||
let lastErrorStrings = lastErrorText.splitToArray(separator: "\n")
|
|
||||||
guard lastErrorStrings.count == 2 && tunnel.activationAttemptId == lastErrorStrings[0] else { return nil }
|
|
||||||
|
|
||||||
switch PacketTunnelProviderError(rawValue: lastErrorStrings[1]) {
|
private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
|
||||||
case .some(.savedProtocolConfigurationIsInvalid):
|
guard let lastErrorFileURL = FileManager.networkExtensionLastErrorFileURL else { return nil }
|
||||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationSavedConfigFailureMessage"))
|
guard let lastErrorData = try? Data(contentsOf: lastErrorFileURL) else { return nil }
|
||||||
case .some(.dnsResolutionFailure):
|
guard let lastErrorStrings = String(data: lastErrorData, encoding: .utf8)?.splitToArray(separator: "\n") else { return nil }
|
||||||
return (tr("alertTunnelDNSFailureTitle"), tr("alertTunnelDNSFailureMessage"))
|
guard lastErrorStrings.count == 2 && tunnel.activationAttemptId == lastErrorStrings[0] else { return nil }
|
||||||
case .some(.couldNotStartBackend):
|
|
||||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationBackendFailureMessage"))
|
switch PacketTunnelProviderError(rawValue: lastErrorStrings[1]) {
|
||||||
case .some(.couldNotDetermineFileDescriptor):
|
case .some(.savedProtocolConfigurationIsInvalid):
|
||||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFileDescriptorFailureMessage"))
|
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationSavedConfigFailureMessage"))
|
||||||
case .some(.couldNotSetNetworkSettings):
|
case .some(.dnsResolutionFailure):
|
||||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationSetNetworkSettingsMessage"))
|
return (tr("alertTunnelDNSFailureTitle"), tr("alertTunnelDNSFailureMessage"))
|
||||||
default:
|
case .some(.couldNotStartBackend):
|
||||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage"))
|
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationBackendFailureMessage"))
|
||||||
}
|
case .some(.couldNotDetermineFileDescriptor):
|
||||||
|
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFileDescriptorFailureMessage"))
|
||||||
|
case .some(.couldNotSetNetworkSettings):
|
||||||
|
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationSetNetworkSettingsMessage"))
|
||||||
|
default:
|
||||||
|
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage"))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let statusObservationToken = statusObservationToken {
|
|
||||||
NotificationCenter.default.removeObserver(statusObservationToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelContainer: NSObject {
|
class TunnelContainer: NSObject {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class TunnelListCell: UITableViewCell {
|
|||||||
|
|
||||||
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func switchToggled() {
|
@objc func switchToggled() {
|
||||||
onSwitchToggled?(statusSwitch.isOn)
|
onSwitchToggled?(statusSwitch.isOn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user