VPN: Error out when tunnel activation fails because there's no internet
Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
parent
cdd132d7d0
commit
e1b258353c
|
@ -44,6 +44,7 @@
|
||||||
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FE219C60290028284D /* ZipExporter.swift */; };
|
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FE219C60290028284D /* ZipExporter.swift */; };
|
||||||
6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC1E211EC472002C96EB /* Assets.xcassets */; };
|
6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC1E211EC472002C96EB /* Assets.xcassets */; };
|
||||||
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */; };
|
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */; };
|
||||||
|
6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF717E421B2CB1E0045A474 /* InternetReachability.swift */; };
|
||||||
6FFA5D8921942F320001E2F7 /* PacketTunnelSettingsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */; };
|
6FFA5D8921942F320001E2F7 /* PacketTunnelSettingsGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */; };
|
||||||
6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.swift */; };
|
6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.swift */; };
|
||||||
6FFA5D8F2194370D0001E2F7 /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E9217229DB006A79B3 /* IPAddressRange.swift */; };
|
6FFA5D8F2194370D0001E2F7 /* IPAddressRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E9217229DB006A79B3 /* IPAddressRange.swift */; };
|
||||||
|
@ -139,6 +140,7 @@
|
||||||
6FF4AC2B211EC776002C96EB /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Config.xcconfig; path = Config/Config.xcconfig; sourceTree = "<group>"; };
|
6FF4AC2B211EC776002C96EB /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Config.xcconfig; path = Config/Config.xcconfig; sourceTree = "<group>"; };
|
||||||
6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
|
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
|
||||||
|
6FF717E421B2CB1E0045A474 /* InternetReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetReachability.swift; sourceTree = "<group>"; };
|
||||||
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
||||||
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
||||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; };
|
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; };
|
||||||
|
@ -248,6 +250,7 @@
|
||||||
children = (
|
children = (
|
||||||
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
||||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
||||||
|
6FF717E421B2CB1E0045A474 /* InternetReachability.swift */,
|
||||||
);
|
);
|
||||||
path = VPN;
|
path = VPN;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -556,6 +559,7 @@
|
||||||
6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
|
6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */,
|
||||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
||||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
||||||
|
6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,8 +21,12 @@ class ErrorPresenter {
|
||||||
return ("Unable to remove tunnel", "Internal error")
|
return ("Unable to remove tunnel", "Internal error")
|
||||||
|
|
||||||
// TunnelActivationError
|
// TunnelActivationError
|
||||||
case TunnelActivationError.tunnelActivationFailed:
|
case TunnelActivationError.tunnelActivationAttemptFailed:
|
||||||
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
||||||
|
case TunnelActivationError.tunnelActivationFailedInternalError:
|
||||||
|
return ("Activation failure", "The tunnel could not be activated due to an internal error")
|
||||||
|
case TunnelActivationError.tunnelActivationFailedNoInternetConnection:
|
||||||
|
return ("Activation failure", "No internet connection")
|
||||||
|
|
||||||
// Importing a zip file
|
// Importing a zip file
|
||||||
case ZipArchiveError.cantOpenInputZipFile:
|
case ZipArchiveError.cantOpenInputZipFile:
|
||||||
|
|
|
@ -80,6 +80,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
busyIndicator.stopAnimating()
|
busyIndicator.stopAnimating()
|
||||||
|
|
||||||
tunnelsManager.delegate = s
|
tunnelsManager.delegate = s
|
||||||
|
tunnelsManager.activationDelegate = s
|
||||||
s.tunnelsManager = tunnelsManager
|
s.tunnelsManager = tunnelsManager
|
||||||
s.onTunnelsManagerReady?(tunnelsManager)
|
s.onTunnelsManagerReady?(tunnelsManager)
|
||||||
s.onTunnelsManagerReady = nil
|
s.onTunnelsManagerReady = nil
|
||||||
|
@ -312,6 +313,14 @@ extension TunnelsListTableViewController: TunnelsManagerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: TunnelActivationDelegate
|
||||||
|
|
||||||
|
extension TunnelsListTableViewController: TunnelActivationDelegate {
|
||||||
|
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelActivationError) {
|
||||||
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TunnelsListTableViewCell: UITableViewCell {
|
class TunnelsListTableViewCell: UITableViewCell {
|
||||||
static let id: String = "TunnelsListTableViewCell"
|
static let id: String = "TunnelsListTableViewCell"
|
||||||
var tunnel: TunnelContainer? {
|
var tunnel: TunnelContainer? {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import SystemConfiguration
|
||||||
|
|
||||||
|
class InternetReachability {
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
case unknown
|
||||||
|
case notReachable
|
||||||
|
case reachableOverWiFi
|
||||||
|
case reachableOverCellular
|
||||||
|
}
|
||||||
|
|
||||||
|
static func currentStatus() -> Status {
|
||||||
|
var status: Status = .unknown
|
||||||
|
if let reachabilityRef = InternetReachability.reachabilityRef() {
|
||||||
|
var flags = SCNetworkReachabilityFlags(rawValue: 0)
|
||||||
|
SCNetworkReachabilityGetFlags(reachabilityRef, &flags)
|
||||||
|
status = Status(reachabilityFlags: flags)
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func reachabilityRef() -> SCNetworkReachability? {
|
||||||
|
let addrIn = sockaddr_in(sin_len: UInt8(MemoryLayout<sockaddr_in>.size),
|
||||||
|
sin_family: sa_family_t(AF_INET),
|
||||||
|
sin_port: 0,
|
||||||
|
sin_addr: in_addr(s_addr: 0),
|
||||||
|
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
||||||
|
return withUnsafePointer(to: addrIn) { (addrInPtr) -> SCNetworkReachability? in
|
||||||
|
addrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { (addrPtr) -> SCNetworkReachability? in
|
||||||
|
return SCNetworkReachabilityCreateWithAddress(nil, addrPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension InternetReachability.Status {
|
||||||
|
init(reachabilityFlags flags: SCNetworkReachabilityFlags) {
|
||||||
|
var status: InternetReachability.Status = .notReachable
|
||||||
|
if (flags.contains(.reachable)) {
|
||||||
|
if (flags.contains(.isWWAN)) {
|
||||||
|
status = .reachableOverCellular
|
||||||
|
} else {
|
||||||
|
status = .reachableOverWiFi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self = status
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,14 @@ protocol TunnelsManagerDelegate: class {
|
||||||
func tunnelRemoved(at: Int)
|
func tunnelRemoved(at: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol TunnelActivationDelegate: class {
|
||||||
|
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelActivationError)
|
||||||
|
}
|
||||||
|
|
||||||
enum TunnelActivationError: Error {
|
enum TunnelActivationError: Error {
|
||||||
case tunnelActivationFailed
|
case tunnelActivationAttemptFailed // startTunnel() throwed
|
||||||
|
case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed
|
||||||
|
case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet
|
||||||
case attemptingActivationWhenTunnelIsNotInactive
|
case attemptingActivationWhenTunnelIsNotInactive
|
||||||
case attemptingDeactivationWhenTunnelIsInactive
|
case attemptingDeactivationWhenTunnelIsInactive
|
||||||
}
|
}
|
||||||
|
@ -30,6 +36,7 @@ class TunnelsManager {
|
||||||
|
|
||||||
private var tunnels: [TunnelContainer]
|
private var tunnels: [TunnelContainer]
|
||||||
weak var delegate: TunnelsManagerDelegate?
|
weak var delegate: TunnelsManagerDelegate?
|
||||||
|
weak var activationDelegate: TunnelActivationDelegate?
|
||||||
|
|
||||||
private var isAddingTunnel: Bool = false
|
private var isAddingTunnel: Bool = false
|
||||||
private var isModifyingTunnel: Bool = false
|
private var isModifyingTunnel: Bool = false
|
||||||
|
@ -212,14 +219,27 @@ class TunnelsManager {
|
||||||
completionHandler(TunnelActivationError.attemptingActivationWhenTunnelIsNotInactive)
|
completionHandler(TunnelActivationError.attemptingActivationWhenTunnelIsNotInactive)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _startActivation(of tunnel: TunnelContainer, completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
tunnel.onActivationCommitted = { [weak self] (success) in
|
||||||
|
if (!success) {
|
||||||
|
let error = (InternetReachability.currentStatus() == .notReachable ?
|
||||||
|
TunnelActivationError.tunnelActivationFailedNoInternetConnection :
|
||||||
|
TunnelActivationError.tunnelActivationFailedInternalError)
|
||||||
|
self?.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tunnel.startActivation(completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
|
||||||
if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
|
if let tunnelInOperation = tunnels.first(where: { $0.status != .inactive }) {
|
||||||
tunnel.status = .waiting
|
tunnel.status = .waiting
|
||||||
tunnelInOperation.onDeactivationComplete = {
|
tunnelInOperation.onDeactivationComplete = {
|
||||||
tunnel.startActivation(completionHandler: completionHandler)
|
_startActivation(of: tunnel, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
startDeactivation(of: tunnelInOperation)
|
startDeactivation(of: tunnelInOperation)
|
||||||
} else {
|
} else {
|
||||||
tunnel.startActivation(completionHandler: completionHandler)
|
_startActivation(of: tunnel, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +269,8 @@ class TunnelContainer: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isAttemptingActivation: Bool = false
|
||||||
|
var onActivationCommitted: ((Bool) -> Void)?
|
||||||
var onDeactivationComplete: (() -> Void)?
|
var onDeactivationComplete: (() -> Void)?
|
||||||
|
|
||||||
fileprivate let tunnelProvider: NETunnelProviderManager
|
fileprivate let tunnelProvider: NETunnelProviderManager
|
||||||
|
@ -289,8 +311,8 @@ class TunnelContainer: NSObject {
|
||||||
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
|
guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() }
|
||||||
|
|
||||||
onDeactivationComplete = nil
|
onDeactivationComplete = nil
|
||||||
startActivation(tunnelConfiguration: tunnelConfiguration,
|
isAttemptingActivation = true
|
||||||
completionHandler: completionHandler)
|
startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func startActivation(recursionCount: UInt = 0,
|
fileprivate func startActivation(recursionCount: UInt = 0,
|
||||||
|
@ -299,7 +321,7 @@ class TunnelContainer: NSObject {
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
if (recursionCount >= 8) {
|
if (recursionCount >= 8) {
|
||||||
os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)")
|
os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)")
|
||||||
completionHandler(TunnelActivationError.tunnelActivationFailed)
|
completionHandler(TunnelActivationError.tunnelActivationAttemptFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,6 +408,18 @@ class TunnelContainer: NSObject {
|
||||||
object: connection,
|
object: connection,
|
||||||
queue: nil) { [weak self] (_) in
|
queue: nil) { [weak self] (_) in
|
||||||
guard let s = self else { return }
|
guard let s = self else { return }
|
||||||
|
if (s.isAttemptingActivation) {
|
||||||
|
if (connection.status == .connecting || connection.status == .connected) {
|
||||||
|
// We tried to start the tunnel, and that attempt is on track to become succeessful
|
||||||
|
s.onActivationCommitted?(true)
|
||||||
|
s.onActivationCommitted = nil
|
||||||
|
} else if (connection.status == .disconnecting || connection.status == .disconnected) {
|
||||||
|
// We tried to start the tunnel, but that attempt didn't succeed
|
||||||
|
s.onActivationCommitted?(false)
|
||||||
|
s.onActivationCommitted = nil
|
||||||
|
}
|
||||||
|
s.isAttemptingActivation = false
|
||||||
|
}
|
||||||
if ((s.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) {
|
if ((s.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) {
|
||||||
// Don't change s.status when disconnecting for a restart
|
// Don't change s.status when disconnecting for a restart
|
||||||
if (connection.status == .disconnected) {
|
if (connection.status == .disconnected) {
|
||||||
|
|
Loading…
Reference in New Issue