TunnelsManager: Report activation errors through the activationDelegate
Don't report activation errors through completion handlers
This commit is contained in:
parent
8d0d8cc11f
commit
b0df5a53da
|
@ -13,7 +13,37 @@ protocol TunnelsManagerListDelegate: class {
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol TunnelsManagerActivationDelegate: class {
|
protocol TunnelsManagerActivationDelegate: class {
|
||||||
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError)
|
func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) // startTunnel wasn't called or failed
|
||||||
|
func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) // startTunnel succeeded
|
||||||
|
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) // status didn't change to connected
|
||||||
|
func tunnelActivationSucceeded(tunnel: TunnelContainer) // status changed to connected
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TunnelsManagerActivationAttemptError: WireGuardAppError {
|
||||||
|
case tunnelIsNotInactive
|
||||||
|
case anotherTunnelIsOperational(otherTunnelName: String)
|
||||||
|
case failedWhileStarting // startTunnel() throwed
|
||||||
|
case failedWhileSaving // save config after re-enabling throwed
|
||||||
|
case failedWhileLoading // reloading config throwed
|
||||||
|
case failedBecauseOfTooManyErrors // recursion limit reached
|
||||||
|
|
||||||
|
func alertText() -> AlertText {
|
||||||
|
switch self {
|
||||||
|
case .tunnelIsNotInactive:
|
||||||
|
return ("Activation failure", "The tunnel is already active or in the process of being activated")
|
||||||
|
case .anotherTunnelIsOperational(let otherTunnelName):
|
||||||
|
return ("Activation failure", "Please disconnect '\(otherTunnelName)' before enabling this tunnel.")
|
||||||
|
case .failedWhileStarting, .failedWhileSaving, .failedWhileLoading, .failedBecauseOfTooManyErrors:
|
||||||
|
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TunnelsManagerActivationError: WireGuardAppError {
|
||||||
|
case activationFailed
|
||||||
|
func alertText() -> AlertText {
|
||||||
|
return ("Activation failure", "The tunnel could not be activated")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TunnelsManagerError: WireGuardAppError {
|
enum TunnelsManagerError: WireGuardAppError {
|
||||||
|
@ -25,14 +55,6 @@ enum TunnelsManagerError: WireGuardAppError {
|
||||||
case systemErrorOnModifyTunnel
|
case systemErrorOnModifyTunnel
|
||||||
case systemErrorOnRemoveTunnel
|
case systemErrorOnRemoveTunnel
|
||||||
|
|
||||||
// Tunnel activation
|
|
||||||
case attemptingActivationWhenTunnelIsNotInactive
|
|
||||||
case attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: String)
|
|
||||||
case tunnelActivationAttemptFailed // startTunnel() throwed
|
|
||||||
case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed
|
|
||||||
case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet
|
|
||||||
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity
|
|
||||||
func alertText() -> AlertText {
|
func alertText() -> AlertText {
|
||||||
switch self {
|
switch self {
|
||||||
case .tunnelNameEmpty:
|
case .tunnelNameEmpty:
|
||||||
|
@ -47,16 +69,6 @@ enum TunnelsManagerError: WireGuardAppError {
|
||||||
return ("Unable to modify tunnel", "Internal error")
|
return ("Unable to modify tunnel", "Internal error")
|
||||||
case .systemErrorOnRemoveTunnel:
|
case .systemErrorOnRemoveTunnel:
|
||||||
return ("Unable to remove tunnel", "Internal error")
|
return ("Unable to remove tunnel", "Internal error")
|
||||||
case .attemptingActivationWhenTunnelIsNotInactive:
|
|
||||||
return ("Activation failure", "The tunnel is already active or in the process of being activated")
|
|
||||||
case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName):
|
|
||||||
return ("Activation failure", "Please disconnect '\(otherTunnelName)' before enabling this tunnel.")
|
|
||||||
case .tunnelActivationAttemptFailed:
|
|
||||||
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
|
||||||
case .tunnelActivationFailedInternalError:
|
|
||||||
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
|
||||||
case .tunnelActivationFailedNoInternetConnection:
|
|
||||||
return ("Activation failure", "No internet connection")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,20 +251,21 @@ class TunnelsManager {
|
||||||
return self.tunnels.first { $0.name == tunnelName }
|
return self.tunnels.first { $0.name == tunnelName }
|
||||||
}
|
}
|
||||||
|
|
||||||
func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
func startActivation(of tunnel: TunnelContainer) {
|
||||||
guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted
|
guard tunnels.contains(tunnel) else { return } // Ensure it's not deleted
|
||||||
guard tunnel.status == .inactive else {
|
guard tunnel.status == .inactive else {
|
||||||
completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive)
|
self.activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .tunnelIsNotInactive)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
|
if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
|
||||||
completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name))
|
self.activationDelegate?.tunnelActivationAttemptFailed(tunnel: tunnel, error: .anotherTunnelIsOperational(otherTunnelName: tunnelInOperation.name))
|
||||||
|
// FIXME: Switches in the UI won't be reset, but we'll be reintroducing waiting, so that's fine
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelBeingActivated = tunnel
|
tunnelBeingActivated = tunnel
|
||||||
tunnel.startActivation(completionHandler: completionHandler)
|
tunnel.startActivation(activationDelegate: self.activationDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDeactivation(of tunnel: TunnelContainer) {
|
func startDeactivation(of tunnel: TunnelContainer) {
|
||||||
|
@ -277,16 +290,14 @@ 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)'")
|
||||||
|
|
||||||
// In case our attempt to start the tunnel, didn't succeed
|
// Track what happened to our attempt to start the tunnel
|
||||||
if tunnel == self.tunnelBeingActivated {
|
if tunnel.isAttemptingActivation {
|
||||||
if session.status == .disconnected {
|
if session.status == .connected {
|
||||||
if InternetReachability.currentStatus() == .notReachable {
|
tunnel.isAttemptingActivation = false
|
||||||
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
|
self.activationDelegate?.tunnelActivationSucceeded(tunnel: tunnel)
|
||||||
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
} else if session.status == .disconnected {
|
||||||
}
|
tunnel.isAttemptingActivation = false
|
||||||
self.tunnelBeingActivated = nil
|
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: .activationFailed)
|
||||||
} else if session.status == .connected {
|
|
||||||
self.tunnelBeingActivated = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +306,7 @@ class TunnelsManager {
|
||||||
// Don't change tunnel.status when disconnecting for a restart
|
// Don't change tunnel.status when disconnecting for a restart
|
||||||
if session.status == .disconnected {
|
if session.status == .disconnected {
|
||||||
self.tunnelBeingActivated = tunnel
|
self.tunnelBeingActivated = tunnel
|
||||||
tunnel.startActivation { _ in }
|
tunnel.startActivation(activationDelegate: self.activationDelegate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -318,8 +329,6 @@ class TunnelContainer: NSObject {
|
||||||
@objc dynamic var isActivateOnDemandEnabled: Bool
|
@objc dynamic var isActivateOnDemandEnabled: Bool
|
||||||
|
|
||||||
var isAttemptingActivation: Bool = false
|
var isAttemptingActivation: Bool = false
|
||||||
var onActivationCommitted: ((Bool) -> Void)?
|
|
||||||
var onDeactivationComplete: (() -> Void)?
|
|
||||||
|
|
||||||
fileprivate let tunnelProvider: NETunnelProviderManager
|
fileprivate let tunnelProvider: NETunnelProviderManager
|
||||||
private var lastTunnelConnectionStatus: NEVPNStatus?
|
private var lastTunnelConnectionStatus: NEVPNStatus?
|
||||||
|
@ -347,21 +356,21 @@ class TunnelContainer: NSObject {
|
||||||
self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled
|
self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func startActivation(completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
fileprivate func startActivation(activationDelegate: TunnelsManagerActivationDelegate?) {
|
||||||
assert(status == .inactive || status == .restarting)
|
assert(status == .inactive || status == .restarting)
|
||||||
|
|
||||||
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
|
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
|
||||||
|
|
||||||
startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
startActivation(tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func startActivation(recursionCount: UInt = 0,
|
fileprivate func startActivation(recursionCount: UInt = 0,
|
||||||
lastError: Error? = nil,
|
lastError: Error? = nil,
|
||||||
tunnelConfiguration: TunnelConfiguration,
|
tunnelConfiguration: TunnelConfiguration,
|
||||||
completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
activationDelegate: TunnelsManagerActivationDelegate?) {
|
||||||
if recursionCount >= 8 {
|
if recursionCount >= 8 {
|
||||||
wg_log(.error, message: "startActivation: Failed after 8 attempts. Giving up with \(lastError!)")
|
wg_log(.error, message: "startActivation: Failed after 8 attempts. Giving up with \(lastError!)")
|
||||||
completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedBecauseOfTooManyErrors)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,15 +382,16 @@ class TunnelContainer: NSObject {
|
||||||
wg_log(.debug, staticMessage: "startActivation: Tunnel is disabled. Re-enabling and saving")
|
wg_log(.debug, staticMessage: "startActivation: Tunnel is disabled. Re-enabling and saving")
|
||||||
tunnelProvider.isEnabled = true
|
tunnelProvider.isEnabled = true
|
||||||
tunnelProvider.saveToPreferences { [weak self] error in
|
tunnelProvider.saveToPreferences { [weak self] error in
|
||||||
|
guard let self = self else { return }
|
||||||
if error != nil {
|
if error != nil {
|
||||||
wg_log(.error, message: "Error saving tunnel after re-enabling: \(error!)")
|
wg_log(.error, message: "Error saving tunnel after re-enabling: \(error!)")
|
||||||
completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileSaving)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wg_log(.debug, staticMessage: "startActivation: Tunnel saved after re-enabling")
|
wg_log(.debug, staticMessage: "startActivation: Tunnel saved after re-enabling")
|
||||||
wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
|
wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
|
||||||
self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown),
|
self.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown),
|
||||||
tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -389,33 +399,36 @@ class TunnelContainer: NSObject {
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
do {
|
do {
|
||||||
wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
|
wg_log(.debug, staticMessage: "startActivation: Starting tunnel")
|
||||||
|
self.isAttemptingActivation = true
|
||||||
try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel()
|
try (tunnelProvider.connection as? NETunnelProviderSession)?.startTunnel()
|
||||||
wg_log(.debug, staticMessage: "startActivation: Success")
|
wg_log(.debug, staticMessage: "startActivation: Success")
|
||||||
completionHandler(nil)
|
activationDelegate?.tunnelActivationAttemptSucceeded(tunnel: self)
|
||||||
} catch let error {
|
} catch let error {
|
||||||
|
self.isAttemptingActivation = false
|
||||||
guard let systemError = error as? NEVPNError else {
|
guard let systemError = error as? NEVPNError else {
|
||||||
wg_log(.error, message: "Failed to activate tunnel: Error: \(error)")
|
wg_log(.error, message: "Failed to activate tunnel: Error: \(error)")
|
||||||
status = .inactive
|
status = .inactive
|
||||||
completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileStarting)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard systemError.code == NEVPNError.configurationInvalid || systemError.code == NEVPNError.configurationStale else {
|
guard systemError.code == NEVPNError.configurationInvalid || systemError.code == NEVPNError.configurationStale else {
|
||||||
wg_log(.error, message: "Failed to activate tunnel: VPN Error: \(error)")
|
wg_log(.error, message: "Failed to activate tunnel: VPN Error: \(error)")
|
||||||
status = .inactive
|
status = .inactive
|
||||||
completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileStarting)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wg_log(.debug, staticMessage: "startActivation: Will reload tunnel and then try to start it.")
|
wg_log(.debug, staticMessage: "startActivation: Will reload tunnel and then try to start it.")
|
||||||
tunnelProvider.loadFromPreferences { [weak self] error in
|
tunnelProvider.loadFromPreferences { [weak self] error in
|
||||||
|
guard let self = self else { return }
|
||||||
if error != nil {
|
if error != nil {
|
||||||
wg_log(.error, message: "startActivation: Error reloading tunnel: \(error!)")
|
wg_log(.error, message: "startActivation: Error reloading tunnel: \(error!)")
|
||||||
self?.status = .inactive
|
self.status = .inactive
|
||||||
completionHandler(TunnelsManagerError.tunnelActivationAttemptFailed)
|
activationDelegate?.tunnelActivationAttemptFailed(tunnel: self, error: .failedWhileLoading)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wg_log(.debug, staticMessage: "startActivation: Tunnel reloaded")
|
wg_log(.debug, staticMessage: "startActivation: Tunnel reloaded")
|
||||||
wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
|
wg_log(.debug, staticMessage: "startActivation: Invoking startActivation")
|
||||||
self?.startActivation(recursionCount: recursionCount + 1, lastError: systemError, tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
self.startActivation(recursionCount: recursionCount + 1, lastError: systemError, tunnelConfiguration: tunnelConfiguration, activationDelegate: activationDelegate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,21 @@ class MainViewController: UISplitViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainViewController: TunnelsManagerActivationDelegate {
|
extension MainViewController: TunnelsManagerActivationDelegate {
|
||||||
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerError) {
|
func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) {
|
||||||
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tunnelActivationSucceeded(tunnel: TunnelContainer) {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainViewController {
|
extension MainViewController {
|
||||||
|
|
|
@ -166,15 +166,7 @@ extension TunnelDetailTableViewController {
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if isOn {
|
if isOn {
|
||||||
self.tunnelsManager.startActivation(of: self.tunnel) { [weak self] error in
|
self.tunnelsManager.startActivation(of: self.tunnel)
|
||||||
if let error = error {
|
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
cell.statusSwitch.isOn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,15 +248,7 @@ extension TunnelsListTableViewController: UITableViewDataSource {
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let self = self, let tunnelsManager = self.tunnelsManager else { return }
|
guard let self = self, let tunnelsManager = self.tunnelsManager else { return }
|
||||||
if isOn {
|
if isOn {
|
||||||
tunnelsManager.startActivation(of: tunnel) { [weak self] error in
|
tunnelsManager.startActivation(of: tunnel)
|
||||||
if let error = error {
|
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
cell.statusSwitch.isOn = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
tunnelsManager.startDeactivation(of: tunnel)
|
tunnelsManager.startDeactivation(of: tunnel)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue