diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index c41bede..d308075 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 6FE254FF219C60290028284D /* ZipExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FE219C60290028284D /* ZipExporter.swift */; }; 6FF4AC1F211EC472002C96EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FF4AC1E211EC472002C96EB /* Assets.xcassets */; }; 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 */; }; 6FFA5D8E2194370D0001E2F7 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E72172020C006A79B3 /* Configuration.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 = ""; }; 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 = ""; }; + 6FF717E421B2CB1E0045A474 /* InternetReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetReachability.swift; sourceTree = ""; }; 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = ""; }; 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = ""; }; 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = ""; }; @@ -248,6 +250,7 @@ children = ( 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */, 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */, + 6FF717E421B2CB1E0045A474 /* InternetReachability.swift */, ); path = VPN; sourceTree = ""; @@ -556,6 +559,7 @@ 6FDEF806218725D200D8FBF6 /* SettingsTableViewController.swift in Sources */, 6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */, 6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */, + 6FF717E521B2CB1E0045A474 /* InternetReachability.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift index 8aa0c1a..1116f61 100644 --- a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift +++ b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift @@ -21,8 +21,12 @@ class ErrorPresenter { return ("Unable to remove tunnel", "Internal error") // TunnelActivationError - case TunnelActivationError.tunnelActivationFailed: + case TunnelActivationError.tunnelActivationAttemptFailed: 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 case ZipArchiveError.cantOpenInputZipFile: diff --git a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift index 6e77433..c2c7b58 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift @@ -80,6 +80,7 @@ class TunnelsListTableViewController: UIViewController { busyIndicator.stopAnimating() tunnelsManager.delegate = s + tunnelsManager.activationDelegate = s s.tunnelsManager = tunnelsManager s.onTunnelsManagerReady?(tunnelsManager) 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 { static let id: String = "TunnelsListTableViewCell" var tunnel: TunnelContainer? { diff --git a/WireGuard/WireGuard/VPN/InternetReachability.swift b/WireGuard/WireGuard/VPN/InternetReachability.swift new file mode 100644 index 0000000..7298d3f --- /dev/null +++ b/WireGuard/WireGuard/VPN/InternetReachability.swift @@ -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.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 + } +} diff --git a/WireGuard/WireGuard/VPN/TunnelsManager.swift b/WireGuard/WireGuard/VPN/TunnelsManager.swift index 0a3dd87..4306fd1 100644 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ b/WireGuard/WireGuard/VPN/TunnelsManager.swift @@ -12,8 +12,14 @@ protocol TunnelsManagerDelegate: class { func tunnelRemoved(at: Int) } +protocol TunnelActivationDelegate: class { + func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelActivationError) +} + 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 attemptingDeactivationWhenTunnelIsInactive } @@ -30,6 +36,7 @@ class TunnelsManager { private var tunnels: [TunnelContainer] weak var delegate: TunnelsManagerDelegate? + weak var activationDelegate: TunnelActivationDelegate? private var isAddingTunnel: Bool = false private var isModifyingTunnel: Bool = false @@ -212,14 +219,27 @@ class TunnelsManager { completionHandler(TunnelActivationError.attemptingActivationWhenTunnelIsNotInactive) 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 }) { tunnel.status = .waiting tunnelInOperation.onDeactivationComplete = { - tunnel.startActivation(completionHandler: completionHandler) + _startActivation(of: tunnel, completionHandler: completionHandler) } startDeactivation(of: tunnelInOperation) } 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)? fileprivate let tunnelProvider: NETunnelProviderManager @@ -289,8 +311,8 @@ class TunnelContainer: NSObject { guard let tunnelConfiguration = tunnelConfiguration() else { fatalError() } onDeactivationComplete = nil - startActivation(tunnelConfiguration: tunnelConfiguration, - completionHandler: completionHandler) + isAttemptingActivation = true + startActivation(tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler) } fileprivate func startActivation(recursionCount: UInt = 0, @@ -299,7 +321,7 @@ class TunnelContainer: NSObject { completionHandler: @escaping (Error?) -> Void) { if (recursionCount >= 8) { os_log("startActivation: Failed after 8 attempts. Giving up with %{public}@", log: OSLog.default, type: .error, "\(lastError!)") - completionHandler(TunnelActivationError.tunnelActivationFailed) + completionHandler(TunnelActivationError.tunnelActivationAttemptFailed) return } @@ -386,6 +408,18 @@ class TunnelContainer: NSObject { object: connection, queue: nil) { [weak self] (_) in 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)) { // Don't change s.status when disconnecting for a restart if (connection.status == .disconnected) {