VPN: Better error and status handling

This commit is contained in:
Roopesh Chander 2018-10-27 18:30:07 +05:30
parent 924b824af4
commit 4516fa0fdd
2 changed files with 58 additions and 98 deletions

View File

@ -156,26 +156,12 @@ extension TunnelDetailTableViewController {
cell.isSwitchInteractionEnabled = false cell.isSwitchInteractionEnabled = false
guard let s = self else { return } guard let s = self else { return }
if (isOn) { if (isOn) {
s.tunnelsManager.activate(tunnel: s.tunnel) { [weak s] isActivated in s.tunnelsManager.startActivation(of: s.tunnel) { error in
if (!isActivated) { print("Error while activating: \(String(describing: error))")
DispatchQueue.main.async {
cell.setSwitchStatus(isOn: false)
}
s?.showErrorAlert(title: "Could not activate",
message: "Could not activate the tunnel because of an internal error")
}
cell.isSwitchInteractionEnabled = true
} }
} else { } else {
s.tunnelsManager.deactivate(tunnel: s.tunnel) { [weak s] isDeactivated in s.tunnelsManager.startDeactivation(of: s.tunnel) { error in
cell.isSwitchInteractionEnabled = true print("Error while deactivating: \(String(describing: error))")
if (!isDeactivated) {
DispatchQueue.main.async {
cell.setSwitchStatus(isOn: true)
}
s?.showErrorAlert(title: "Could not deactivate",
message: "Could not deactivate the tunnel because of an internal error")
}
} }
} }
} }
@ -251,10 +237,6 @@ class TunnelDetailTableViewStatusCell: UITableViewCell {
var onSwitchToggled: ((Bool) -> Void)? = nil var onSwitchToggled: ((Bool) -> Void)? = nil
private var isOnSwitchToggledHandlerEnabled: Bool = true private var isOnSwitchToggledHandlerEnabled: Bool = true
func setSwitchStatus(isOn: Bool) {
statusSwitch.isOn = isOn
}
let statusSwitch: UISwitch let statusSwitch: UISwitch
private var statusObservervationToken: AnyObject? = nil private var statusObservervationToken: AnyObject? = nil
@ -289,13 +271,15 @@ class TunnelDetailTableViewStatusCell: UITableViewCell {
text = "Deactivating" text = "Deactivating"
case .reasserting: case .reasserting:
text = "Reactivating" text = "Reactivating"
case .waitingForOtherDeactivation:
text = "Waiting"
case .resolvingEndpointDomains: case .resolvingEndpointDomains:
text = "Resolving domains" text = "Resolving domains"
} }
textLabel?.text = text textLabel?.text = text
setSwitchStatus(isOn: !(status == .deactivating || status == .inactive)) DispatchQueue.main.async { [weak statusSwitch] in
guard let statusSwitch = statusSwitch else { return }
statusSwitch.isOn = !(status == .deactivating || status == .inactive)
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active || status == .resolvingEndpointDomains)
}
textLabel?.textColor = (status == .active || status == .inactive) ? UIColor.black : UIColor.gray textLabel?.textColor = (status == .active || status == .inactive) ? UIColor.black : UIColor.gray
} }

View File

@ -11,6 +11,15 @@ protocol TunnelsManagerDelegate: class {
func tunnelsChanged() func tunnelsChanged()
} }
enum TunnelsManagerError: Error {
case dnsResolutionFailed
case dnsResolutionCancelled
case tunnelOperationFailed
case attemptingActivationWhenAnotherTunnelIsActive
case attemptingActivationWhenTunnelIsNotInactive
case attemptingDeactivationWhenTunnelIsInactive
}
class TunnelsManager { class TunnelsManager {
var tunnels: [TunnelContainer] var tunnels: [TunnelContainer]
@ -20,12 +29,8 @@ class TunnelsManager {
private var isModifyingTunnel: Bool = false private var isModifyingTunnel: Bool = false
private var isDeletingTunnel: Bool = false private var isDeletingTunnel: Bool = false
private var currentlyActiveTunnel: TunnelContainer? private var currentTunnel: TunnelContainer?
private var tunnelStatusObservationToken: AnyObject? private var currentTunnelStatusObservationToken: AnyObject?
enum TunnelsManagerError: Error {
case tunnelsUninitialized
}
init(tunnelProviders: [NETunnelProviderManager]) { init(tunnelProviders: [NETunnelProviderManager]) {
var tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0, index: 0) } var tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0, index: 0) }
@ -152,50 +157,34 @@ class TunnelsManager {
return tunnels[index] return tunnels[index]
} }
func activate(tunnel: TunnelContainer, completionHandler: @escaping (Bool) -> Void) { func startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
guard (tunnel.status == .inactive) else { guard (tunnel.status == .inactive) else {
completionHandler(false) completionHandler(TunnelsManagerError.attemptingActivationWhenTunnelIsNotInactive)
return return
} }
if let currentlyActiveTunnel = currentlyActiveTunnel { guard (currentTunnel == nil) else {
assert(tunnel.index != currentlyActiveTunnel.index) completionHandler(TunnelsManagerError.attemptingActivationWhenAnotherTunnelIsActive)
tunnel.status = .waitingForOtherDeactivation return
currentlyActiveTunnel.deactivate { [weak self] isDeactivated in }
guard let s = self, isDeactivated else { tunnel.startActivation(completionHandler: completionHandler)
completionHandler(false) currentTunnel = tunnel
return currentTunnelStatusObservationToken = tunnel.observe(\.status) { [weak self] (tunnel, change) in
} guard let s = self else { return }
tunnel.activate { [weak s] (isActivated) in if (tunnel.status == .inactive) {
if (isActivated) { s.currentTunnel = nil
s?.currentlyActiveTunnel = tunnel s.currentTunnelStatusObservationToken = nil
}
completionHandler(isActivated)
}
}
} else {
tunnel.activate { [weak self] (isActivated) in
if (isActivated) {
self?.currentlyActiveTunnel = tunnel
}
completionHandler(isActivated)
} }
} }
} }
func deactivate(tunnel: TunnelContainer, completionHandler: @escaping (Bool) -> Void) { func startDeactivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
guard let currentlyActiveTunnel = currentlyActiveTunnel else { if (tunnel.status == .inactive) {
completionHandler(false) completionHandler(TunnelsManagerError.attemptingDeactivationWhenTunnelIsInactive)
return return
} }
assert(tunnel.index == currentlyActiveTunnel.index) assert(tunnel.index == currentTunnel!.index)
guard (tunnel.status != .inactive) else {
completionHandler(false) tunnel.startDeactivation()
return
}
tunnel.deactivate { [weak self] isDeactivated in
self?.currentlyActiveTunnel = nil
completionHandler(isDeactivated)
}
} }
} }
@ -232,9 +221,6 @@ class TunnelContainer: NSObject {
fileprivate var index: Int fileprivate var index: Int
fileprivate var statusObservationToken: AnyObject? fileprivate var statusObservationToken: AnyObject?
private var onActive: ((Bool) -> Void)? = nil
private var onInactive: ((Bool) -> Void)? = nil
private var dnsResolver: DNSResolver? = nil private var dnsResolver: DNSResolver? = nil
init(tunnel: NETunnelProviderManager, index: Int) { init(tunnel: NETunnelProviderManager, index: Int) {
@ -253,7 +239,7 @@ class TunnelContainer: NSObject {
return (tunnelProvider.protocolConfiguration as! NETunnelProviderProtocol).tunnelConfiguration() return (tunnelProvider.protocolConfiguration as! NETunnelProviderProtocol).tunnelConfiguration()
} }
fileprivate func activate(completionHandler: @escaping (Bool) -> Void) { fileprivate func startActivation(completionHandler: @escaping (Error?) -> Void) {
assert(status == .inactive) assert(status == .inactive)
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() } guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
let endpoints = tunnelConfiguration.peers.map { $0.endpoint } let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
@ -262,18 +248,17 @@ class TunnelContainer: NSObject {
self.dnsResolver = dnsResolver self.dnsResolver = dnsResolver
status = .resolvingEndpointDomains status = .resolvingEndpointDomains
dnsResolver.resolve { [weak self] endpoints in dnsResolver.resolve { [weak self] endpoints in
guard let endpoints = endpoints else { guard let s = self else { return }
// TODO: Show error message if (s.dnsResolver == nil) {
completionHandler(false) s.status = .inactive
completionHandler(TunnelsManagerError.dnsResolutionCancelled)
return return
} }
guard let s = self else { guard let endpoints = endpoints else {
completionHandler(false) completionHandler(TunnelsManagerError.dnsResolutionFailed)
return return
} }
s.dnsResolver = nil s.dnsResolver = nil
assert(s.onActive == nil)
s.onActive = completionHandler
s.startObservingTunnelStatus() s.startObservingTunnelStatus()
let session = (s.tunnelProvider.connection as! NETunnelProviderSession) let session = (s.tunnelProvider.connection as! NETunnelProviderSession)
do { do {
@ -282,19 +267,18 @@ class TunnelContainer: NSObject {
try session.startTunnel(options: tunnelOptions) try session.startTunnel(options: tunnelOptions)
} catch (let error) { } catch (let error) {
os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)") os_log("Failed to activate tunnel: %{public}@", log: OSLog.default, type: .debug, "\(error)")
s.onActive = nil completionHandler(error)
completionHandler(false)
} }
} }
} }
fileprivate func deactivate(completionHandler: @escaping (Bool) -> Void) { fileprivate func startDeactivation() {
assert(status == .active) if (status != .inactive) {
assert(onInactive == nil) dnsResolver = nil
onInactive = completionHandler assert(statusObservationToken != nil)
assert(statusObservationToken != nil) let session = (tunnelProvider.connection as! NETunnelProviderSession)
let session = (tunnelProvider.connection as! NETunnelProviderSession) session.stopTunnel()
session.stopTunnel() }
} }
private func startObservingTunnelStatus() { private func startObservingTunnelStatus() {
@ -306,16 +290,7 @@ class TunnelContainer: NSObject {
let status = TunnelStatus(from: connection.status) let status = TunnelStatus(from: connection.status)
if let s = self { if let s = self {
s.status = status s.status = status
if (status == .active) { if (status == .inactive) {
s.onActive?(true)
s.onInactive?(false)
s.onActive = nil
s.onInactive = nil
} else if (status == .inactive) {
s.onActive?(false)
s.onInactive?(true)
s.onActive = nil
s.onInactive = nil
s.stopObservingTunnelStatus() s.stopObservingTunnelStatus()
} }
} }
@ -323,7 +298,9 @@ class TunnelContainer: NSObject {
} }
private func stopObservingTunnelStatus() { private func stopObservingTunnelStatus() {
statusObservationToken = nil DispatchQueue.main.async { [weak self] in
self?.statusObservationToken = nil
}
} }
} }
@ -334,7 +311,6 @@ class TunnelContainer: NSObject {
case deactivating case deactivating
case reasserting // On editing an active tunnel, the tunnel shall deactive and then activate case reasserting // On editing an active tunnel, the tunnel shall deactive and then activate
case waitingForOtherDeactivation // Waiting to activate; waiting for deactivation of another tunnel
case resolvingEndpointDomains // DNS resolution in progress case resolvingEndpointDomains // DNS resolution in progress
init(from vpnStatus: NEVPNStatus) { init(from vpnStatus: NEVPNStatus) {