VPN: Better error and status handling
This commit is contained in:
parent
924b824af4
commit
4516fa0fdd
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue