summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj18
-rw-r--r--ios/MullvadVPN/ConnectViewController.swift32
-rw-r--r--ios/MullvadVPN/RelaySelector.swift12
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProviderHost.swift56
-rw-r--r--ios/MullvadVPN/TunnelIPC/PacketTunnelStatus.swift36
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelConnectionInfo.swift17
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelIPCRequest.swift8
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift6
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift36
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift6
-rw-r--r--ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift178
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManagerState.swift18
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift69
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider.swift77
16 files changed, 397 insertions, 178 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 80a279f542..427ae1c59e 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -151,11 +151,11 @@
585DA89426B0323E00B8C587 /* TunnelIPCRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89226B0323E00B8C587 /* TunnelIPCRequest.swift */; };
585DA89626B0328000B8C587 /* TunnelIPCResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89526B0328000B8C587 /* TunnelIPCResponse.swift */; };
585DA89726B0328000B8C587 /* TunnelIPCResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89526B0328000B8C587 /* TunnelIPCResponse.swift */; };
- 585DA89926B0329200B8C587 /* TunnelConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* TunnelConnectionInfo.swift */; };
- 585DA89A26B0329200B8C587 /* TunnelConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* TunnelConnectionInfo.swift */; };
+ 585DA89926B0329200B8C587 /* PacketTunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */; };
+ 585DA89A26B0329200B8C587 /* PacketTunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */; };
585DA89B26B146B300B8C587 /* TunnelIPCCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA88E26B031E200B8C587 /* TunnelIPCCoding.swift */; };
585DA8A326B14E0D00B8C587 /* ServerRelaysResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA88326B0270700B8C587 /* ServerRelaysResponse.swift */; };
- 585DA8A526B14EE000B8C587 /* TunnelConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* TunnelConnectionInfo.swift */; };
+ 585DA8A526B14EE000B8C587 /* PacketTunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */; };
585DA8A626B14F5100B8C587 /* SSLPinningURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */; };
5860392926DCE7AB00554C79 /* PromiseCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860392826DCE7AB00554C79 /* PromiseCompletion.swift */; };
5860392A26DCE7AB00554C79 /* PromiseCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5860392826DCE7AB00554C79 /* PromiseCompletion.swift */; };
@@ -462,7 +462,7 @@
585DA88E26B031E200B8C587 /* TunnelIPCCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelIPCCoding.swift; sourceTree = "<group>"; };
585DA89226B0323E00B8C587 /* TunnelIPCRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelIPCRequest.swift; sourceTree = "<group>"; };
585DA89526B0328000B8C587 /* TunnelIPCResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelIPCResponse.swift; sourceTree = "<group>"; };
- 585DA89826B0329200B8C587 /* TunnelConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelConnectionInfo.swift; sourceTree = "<group>"; };
+ 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelStatus.swift; sourceTree = "<group>"; };
585DA8AE26B9492500B8C587 /* Promise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = "<group>"; };
5860392826DCE7AB00554C79 /* PromiseCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseCompletion.swift; sourceTree = "<group>"; };
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; };
@@ -800,7 +800,7 @@
585DA88D26B031D100B8C587 /* TunnelIPC */ = {
isa = PBXGroup;
children = (
- 585DA89826B0329200B8C587 /* TunnelConnectionInfo.swift */,
+ 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */,
5845F841236CBACD00B2D93C /* TunnelIPC.swift */,
585DA88E26B031E200B8C587 /* TunnelIPCCoding.swift */,
5875960626F36B3A00BF6711 /* TunnelIPCError.swift */,
@@ -979,6 +979,7 @@
5807E2BF2432038B00F5FF30 /* String+Split.swift */,
5871FB8225498CA20051A0A4 /* Swizzle.swift */,
5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */,
+ 58E0A98727C8F46300FE6BDD /* Tunnel.swift */,
585DA88D26B031D100B8C587 /* TunnelIPC */,
5823FA5726CE4A4100283BF8 /* TunnelManager */,
587AD7C523421D7000E93A53 /* TunnelSettings.swift */,
@@ -991,7 +992,6 @@
58FD5BF12424F7D700112C88 /* UserInterfaceInteractionRestriction.swift */,
58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */,
5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */,
- 58E0A98727C8F46300FE6BDD /* Tunnel.swift */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -1336,7 +1336,7 @@
58A94AE626D23C3D001CB97C /* PromiseTests.swift in Sources */,
5857F23824C8446700CF6F47 /* AsyncBlockOperation.swift in Sources */,
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */,
- 585DA8A526B14EE000B8C587 /* TunnelConnectionInfo.swift in Sources */,
+ 585DA8A526B14EE000B8C587 /* PacketTunnelStatus.swift in Sources */,
588DD76D26FCB4A2006F6233 /* Cancellable.swift in Sources */,
5896AE7E246ACE65005B36CB /* KeychainAttributes.swift in Sources */,
58B0A2A9238EE6A100BC001D /* RelayConstraints.swift in Sources */,
@@ -1362,7 +1362,7 @@
58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */,
5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */,
587B75412668FD7800DEF7E9 /* AccountExpiryNotificationProvider.swift in Sources */,
- 585DA89926B0329200B8C587 /* TunnelConnectionInfo.swift in Sources */,
+ 585DA89926B0329200B8C587 /* PacketTunnelStatus.swift in Sources */,
58BA692E23E99EFF009DC256 /* Locking.swift in Sources */,
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */,
5840250122B1124600E4CFEC /* IPAddress+Codable.swift in Sources */,
@@ -1581,7 +1581,7 @@
5838318B27C40A3900000571 /* Pinger.swift in Sources */,
5820675C26E6576800655B05 /* RelayCache.swift in Sources */,
58FAEDF1245069CA00CB0F5B /* KeychainAttributes.swift in Sources */,
- 585DA89A26B0329200B8C587 /* TunnelConnectionInfo.swift in Sources */,
+ 585DA89A26B0329200B8C587 /* PacketTunnelStatus.swift in Sources */,
585DA88526B0270700B8C587 /* ServerRelaysResponse.swift in Sources */,
5806767627048E7D00C858CB /* Promise+Result.swift in Sources */,
581503A724D6F4AE00C9C50E /* Logging.swift in Sources */,
diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift
index 03ca5a2563..59978ec4ad 100644
--- a/ios/MullvadVPN/ConnectViewController.swift
+++ b/ios/MullvadVPN/ConnectViewController.swift
@@ -208,30 +208,30 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen
private func updateTunnelConnectionInfo() {
switch tunnelState {
- case .connecting(let connectionInfo):
- setConnectionInfo(connectionInfo)
+ case .connecting(let tunnelRelay):
+ setTunnelRelay(tunnelRelay)
- case .connected(let connectionInfo), .reconnecting(let connectionInfo):
- setConnectionInfo(connectionInfo)
+ case .connected(let tunnelRelay), .reconnecting(let tunnelRelay):
+ setTunnelRelay(tunnelRelay)
case .disconnected, .disconnecting, .pendingReconnect:
- setConnectionInfo(nil)
+ setTunnelRelay(nil)
}
mainContentView.locationContainerView.accessibilityLabel = tunnelState.localizedAccessibilityLabel
}
- private func setConnectionInfo(_ connectionInfo: TunnelConnectionInfo?) {
- if let connectionInfo = connectionInfo {
- mainContentView.cityLabel.attributedText = attributedStringForLocation(string: connectionInfo.location.city)
- mainContentView.countryLabel.attributedText = attributedStringForLocation(string: connectionInfo.location.country)
+ private func setTunnelRelay(_ tunnelRelay: PacketTunnelRelay?) {
+ if let tunnelRelay = tunnelRelay {
+ mainContentView.cityLabel.attributedText = attributedStringForLocation(string: tunnelRelay.location.city)
+ mainContentView.countryLabel.attributedText = attributedStringForLocation(string: tunnelRelay.location.country)
mainContentView.connectionPanel.dataSource = ConnectionPanelData(
- inAddress: "\(connectionInfo.ipv4Relay) UDP",
+ inAddress: "\(tunnelRelay.ipv4Relay) UDP",
outAddress: nil
)
mainContentView.connectionPanel.isHidden = false
- mainContentView.connectionPanel.connectedRelayName = connectionInfo.hostname
+ mainContentView.connectionPanel.connectedRelayName = tunnelRelay.hostname
} else {
mainContentView.countryLabel.attributedText = attributedStringForLocation(string: " ")
mainContentView.cityLabel.attributedText = attributedStringForLocation(string: " ")
@@ -274,15 +274,15 @@ class ConnectViewController: UIViewController, MKMapViewDelegate, RootContainmen
private func updateLocation(animated: Bool) {
switch tunnelState {
- case .connecting(let connectionInfo):
- if let connectionInfo = connectionInfo {
- setLocation(coordinate: connectionInfo.location.geoCoordinate, animated: animated)
+ case .connecting(let tunnelRelay):
+ if let tunnelRelay = tunnelRelay {
+ setLocation(coordinate: tunnelRelay.location.geoCoordinate, animated: animated)
} else {
unsetLocation(animated: animated)
}
- case .connected(let connectionInfo), .reconnecting(let connectionInfo):
- setLocation(coordinate: connectionInfo.location.geoCoordinate, animated: animated)
+ case .connected(let tunnelRelay), .reconnecting(let tunnelRelay):
+ setLocation(coordinate: tunnelRelay.location.geoCoordinate, animated: animated)
case .disconnected, .disconnecting, .pendingReconnect:
unsetLocation(animated: animated)
diff --git a/ios/MullvadVPN/RelaySelector.swift b/ios/MullvadVPN/RelaySelector.swift
index f0bb12ce7f..fdf587e6d4 100644
--- a/ios/MullvadVPN/RelaySelector.swift
+++ b/ios/MullvadVPN/RelaySelector.swift
@@ -21,12 +21,12 @@ private struct RelayWithLocation {
}
extension RelaySelectorResult {
- var tunnelConnectionInfo: TunnelConnectionInfo {
- return TunnelConnectionInfo(
- ipv4Relay: self.endpoint.ipv4Relay,
- ipv6Relay: self.endpoint.ipv6Relay,
- hostname: self.relay.hostname,
- location: self.location
+ var packetTunnelRelay: PacketTunnelRelay {
+ return PacketTunnelRelay(
+ ipv4Relay: endpoint.ipv4Relay,
+ ipv6Relay: endpoint.ipv6Relay,
+ hostname: relay.hostname,
+ location: location
)
}
}
diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
index cc1cbeef52..7305dbe0f2 100644
--- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
@@ -14,7 +14,7 @@ import Logging
class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
- private var connectionInfo: TunnelConnectionInfo?
+ private var tunnelStatus = PacketTunnelStatus()
private let providerLogger = Logger(label: "SimulatorTunnelProviderHost")
private let stateQueue = DispatchQueue(label: "SimulatorTunnelProviderHostQueue")
@@ -31,9 +31,9 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
if let appSelectorResult = appSelectorResult.flattenValue {
- self.connectionInfo = appSelectorResult.tunnelConnectionInfo
+ self.tunnelStatus.tunnelRelay = appSelectorResult.packetTunnelRelay
} else {
- self.connectionInfo = self.pickRelay()?.tunnelConnectionInfo
+ self.tunnelStatus.tunnelRelay = self.pickRelay()?.packetTunnelRelay
}
completionHandler(nil)
@@ -42,41 +42,41 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
stateQueue.async {
- self.connectionInfo = nil
+ self.tunnelStatus = PacketTunnelStatus()
completionHandler()
}
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
- Result { try TunnelIPC.Coding.decodeRequest(messageData) }
- .asPromise()
- .receive(on: stateQueue)
- .onFailure { error in
+ stateQueue.async {
+ let request: TunnelIPC.Request
+ do {
+ request = try TunnelIPC.Coding.decodeRequest(messageData)
+ } catch {
self.providerLogger.error(chainedError: AnyChainedError(error), message: "Failed to decode the IPC request.")
+ completionHandler?(nil)
+ return
}
- .success()
- .mapThen(defaultValue: nil) { request in
- switch request {
- case .tunnelConnectionInfo:
- return Result { try TunnelIPC.Coding.encodeResponse(self.connectionInfo) }
- .asPromise()
- .onFailure { error in
- self.providerLogger.error(chainedError: AnyChainedError(error), message: "Failed to encode tunnel connection info IPC response.")
- }
- .success()
- case .reloadTunnelSettings:
- self.reasserting = true
- self.connectionInfo = self.pickRelay()?.tunnelConnectionInfo
- self.reasserting = false
+ var response: Data?
- return .resolved(nil)
+ switch request {
+ case .getTunnelStatus:
+ do {
+ response = try TunnelIPC.Coding.encodeResponse(self.tunnelStatus)
+ } catch {
+ self.providerLogger.error(chainedError: AnyChainedError(error), message: "Failed to encode tunnel status IPC response.")
}
+
+ case .reloadTunnelSettings:
+ self.reasserting = true
+ self.tunnelStatus.tunnelRelay = self.pickRelay()?.packetTunnelRelay
+ self.reasserting = false
}
- .observe { completion in
- completionHandler?(completion.unwrappedValue ?? nil)
- }
+
+ completionHandler?(response)
+ }
}
private func pickRelay() -> RelaySelectorResult? {
@@ -93,13 +93,13 @@ class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
constraints: entry.tunnelSettings.relayConstraints
)
case .failure(let error):
- self.providerLogger.error(chainedError: error, message: "Failed to load tunnel settings when picking relay")
+ self.providerLogger.error(chainedError: error, message: "Failed to load tunnel settings when picking relay.")
return nil
}
case .failure(let error):
- self.providerLogger.error(chainedError: error, message: "Failed to read relays when picking relay")
+ self.providerLogger.error(chainedError: error, message: "Failed to read relays when picking relay.")
return nil
}
}
diff --git a/ios/MullvadVPN/TunnelIPC/PacketTunnelStatus.swift b/ios/MullvadVPN/TunnelIPC/PacketTunnelStatus.swift
new file mode 100644
index 0000000000..3dc17a587d
--- /dev/null
+++ b/ios/MullvadVPN/TunnelIPC/PacketTunnelStatus.swift
@@ -0,0 +1,36 @@
+//
+// PacketTunnelStatus.swift
+// PacketTunnelStatus
+//
+// Created by pronebird on 27/07/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// A struct that holds packet tunnel process status.
+struct PacketTunnelStatus: Codable, Equatable {
+ /// Flag indicating whether network is reachable.
+ var isNetworkReachable: Bool
+
+ /// When the packet tunnel started connecting.
+ var connectingDate: Date?
+
+ /// Current relay.
+ var tunnelRelay: PacketTunnelRelay?
+}
+
+/// A struct that holds the relay endpoints and location.
+struct PacketTunnelRelay: Codable, Equatable {
+ /// IPv4 relay endpoint.
+ let ipv4Relay: IPv4Endpoint
+
+ /// IPv6 relay endpoint.
+ let ipv6Relay: IPv6Endpoint?
+
+ /// Relay hostname.
+ let hostname: String
+
+ /// Relay location.
+ let location: Location
+}
diff --git a/ios/MullvadVPN/TunnelIPC/TunnelConnectionInfo.swift b/ios/MullvadVPN/TunnelIPC/TunnelConnectionInfo.swift
deleted file mode 100644
index 9558864e6a..0000000000
--- a/ios/MullvadVPN/TunnelIPC/TunnelConnectionInfo.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// TunnelConnectionInfo.swift
-// TunnelConnectionInfo
-//
-// Created by pronebird on 27/07/2021.
-// Copyright © 2021 Mullvad VPN AB. All rights reserved.
-//
-
-import Foundation
-
-/// A struct that holds basic information regarding the tunnel connection.
-struct TunnelConnectionInfo: Codable, Equatable {
- let ipv4Relay: IPv4Endpoint
- let ipv6Relay: IPv6Endpoint?
- let hostname: String
- let location: Location
-}
diff --git a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequest.swift b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequest.swift
index 7bfa4de803..f331e7d1f5 100644
--- a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequest.swift
+++ b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequest.swift
@@ -14,15 +14,15 @@ extension TunnelIPC {
/// Request the tunnel to reload settings.
case reloadTunnelSettings
- /// Request the tunnel connection info.
- case tunnelConnectionInfo
+ /// Request the tunnel status.
+ case getTunnelStatus
var description: String {
switch self {
case .reloadTunnelSettings:
return "reloadTunnelSettings"
- case .tunnelConnectionInfo:
- return "tunnelConnectionInfo"
+ case .getTunnelStatus:
+ return "getTunnelStatus"
}
}
diff --git a/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift b/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift
index 5847af8b5c..ce430b5b81 100644
--- a/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift
+++ b/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift
@@ -37,11 +37,11 @@ extension TunnelIPC {
}
}
- func getTunnelConnectionInfo(completionHandler: @escaping (OperationCompletion<TunnelConnectionInfo?, TunnelIPC.Error>) -> Void) -> Cancellable {
- let operation = RequestOperation<TunnelConnectionInfo?>(
+ func getTunnelStatus(completionHandler: @escaping (OperationCompletion<PacketTunnelStatus, TunnelIPC.Error>) -> Void) -> Cancellable {
+ let operation = RequestOperation<PacketTunnelStatus>(
queue: queue,
tunnel: tunnel,
- request: .tunnelConnectionInfo,
+ request: .getTunnelStatus,
options: TunnelIPC.RequestOptions(),
completionHandler: completionHandler
)
diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
index d69e2d1028..bef06ac128 100644
--- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
@@ -48,7 +48,7 @@ class MapConnectionStatusOperation: AsyncOperation {
return
}
- let tunnelState = state.tunnelState
+ let tunnelState = state.tunnelStatus.state
switch connectionStatus {
case .connecting:
@@ -56,17 +56,19 @@ class MapConnectionStatusOperation: AsyncOperation {
case .connecting(.some(_)):
break
default:
- state.tunnelState = .connecting(nil)
+ state.tunnelStatus.state = .connecting(nil)
}
let session = TunnelIPC.Session(tunnel: tunnel)
- request = session.getTunnelConnectionInfo { [weak self] completion in
+ request = session.getTunnelStatus { [weak self] completion in
guard let self = self else { return }
self.queue.async {
- if case .success(.some(let connectionInfo)) = completion, !self.isCancelled {
- self.state.tunnelState = .connecting(connectionInfo)
+ if case .success(let packetTunnelStatus) = completion, !self.isCancelled {
+ self.state.tunnelStatus.update(from: packetTunnelStatus) { relay in
+ return .connecting(relay)
+ }
}
self.finish()
@@ -76,12 +78,14 @@ class MapConnectionStatusOperation: AsyncOperation {
case .reasserting:
let session = TunnelIPC.Session(tunnel: tunnel)
- request = session.getTunnelConnectionInfo { [weak self] completion in
+ request = session.getTunnelStatus { [weak self] completion in
guard let self = self else { return }
self.queue.async {
- if case .success(.some(let connectionInfo)) = completion, !self.isCancelled {
- self.state.tunnelState = .reconnecting(connectionInfo)
+ if case .success(let packetTunnelStatus) = completion, !self.isCancelled {
+ self.state.tunnelStatus.update(from: packetTunnelStatus) { relay in
+ return relay.map { .reconnecting($0) }
+ }
}
self.finish()
@@ -93,12 +97,14 @@ class MapConnectionStatusOperation: AsyncOperation {
case .connected:
let session = TunnelIPC.Session(tunnel: tunnel)
- request = session.getTunnelConnectionInfo { [weak self] completion in
+ request = session.getTunnelStatus { [weak self] completion in
guard let self = self else { return }
self.queue.async {
- if case .success(.some(let connectionInfo)) = completion, !self.isCancelled {
- self.state.tunnelState = .connected(connectionInfo)
+ if case .success(let packetTunnelStatus) = completion, !self.isCancelled {
+ self.state.tunnelStatus.update(from: packetTunnelStatus) { relay in
+ return relay.map { .connected($0) }
+ }
}
self.finish()
@@ -115,13 +121,13 @@ class MapConnectionStatusOperation: AsyncOperation {
case .disconnecting(.reconnect):
logger.debug("Restart the tunnel on disconnect.")
- state.tunnelState = .pendingReconnect
+ state.tunnelStatus.reset(to: .pendingReconnect)
startTunnelHandler?()
startTunnelHandler = nil
default:
- state.tunnelState = .disconnected
+ state.tunnelStatus.reset(to: .disconnected)
}
case .disconnecting:
@@ -129,11 +135,11 @@ class MapConnectionStatusOperation: AsyncOperation {
case .disconnecting:
break
default:
- state.tunnelState = .disconnecting(.nothing)
+ state.tunnelStatus.reset(to: .disconnecting(.nothing))
}
case .invalid:
- state.tunnelState = .disconnected
+ state.tunnelStatus.reset(to: .disconnected)
@unknown default:
logger.debug("Unknown NEVPNStatus: \(connectionStatus.rawValue)")
diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
index b93eca2e72..162ebfff73 100644
--- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift
@@ -165,7 +165,7 @@ class SetAccountOperation: AsyncOperation {
willDeleteVPNConfigurationHandler = nil
// Reset tunnel state to disconnected
- state.tunnelState = .disconnected
+ state.tunnelStatus.reset(to: .disconnected)
// Remove tunnel info
state.tunnelInfo = nil
diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
index 2a332aab0b..ac8223f963 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -48,9 +48,9 @@ class StartTunnelOperation: AsyncOperation {
return
}
- switch self.state.tunnelState {
+ switch self.state.tunnelStatus.state {
case .disconnecting(.nothing):
- self.state.tunnelState = .disconnecting(.reconnect)
+ self.state.tunnelStatus.state = .disconnecting(.reconnect)
completionHandler(.success(()))
@@ -115,7 +115,7 @@ class StartTunnelOperation: AsyncOperation {
encodeErrorHandler = nil
state.setTunnel(Tunnel(tunnelProvider: tunnelProvider), shouldRefreshTunnelState: false)
- state.tunnelState = .connecting(selectorResult.tunnelConnectionInfo)
+ state.tunnelStatus.reset(to: .connecting(selectorResult.packetTunnelRelay))
try tunnelProvider.connection.startVPNTunnel(options: tunnelOptions.rawOptions())
}
diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
index 2d75946e7f..7593c0a02b 100644
--- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift
@@ -43,9 +43,9 @@ class StopTunnelOperation: AsyncOperation {
return
}
- switch self.state.tunnelState {
+ switch self.state.tunnelStatus.state {
case .disconnecting(.reconnect):
- state.tunnelState = .disconnecting(.nothing)
+ state.tunnelStatus.state = .disconnecting(.nothing)
completionHandler(.success(()))
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 0e4a379116..d08e273475 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -13,16 +13,32 @@ import UIKit
import Logging
import class WireGuardKitTypes.PublicKey
-/// A class that provides a convenient interface for VPN tunnels configuration, manipulation and
-/// monitoring.
-class TunnelManager: TunnelManagerStateDelegate
-{
- /// Private key rotation interval (in seconds)
- private static let privateKeyRotationInterval: TimeInterval = 60 * 60 * 24 * 4
+enum TunnelManagerConfiguration {
+ /// Delay used before starting to quickly poll the tunnel (in seconds).
+ /// Usually when the tunnel is either starting or when reconnecting for a brief moment, until
+ /// the tunnel broadcasts the connecting date which is later used to synchronize polling.
+ static let tunnelStatusQuickPollDelay: TimeInterval = 1
+
+ /// Poll interval used when connecting date is unknown (in seconds).
+ static let tunnelStatusQuickPollInterval: TimeInterval = 3
+
+ /// Delay used for when connecting date is known (in seconds).
+ /// Since both GUI and packet tunnel run timers, this accounts for some leeway.
+ static let tunnelStatusLongPollDelay: TimeInterval = 0.25
- /// Private key rotation retry interval (in seconds)
- private static let privateKeyRotationFailureRetryInterval: TimeInterval = 60 * 15
+ /// Poll interval used for when connecting date is known (in seconds).
+ static let tunnelStatusLongPollInterval = TunnelMonitorConfiguration.connectionTimeout
+
+ /// Private key rotation interval (in seconds).
+ static let privateKeyRotationInterval: TimeInterval = 60 * 60 * 24 * 4
+
+ /// Private key rotation retry interval (in seconds).
+ static let privateKeyRotationFailureRetryInterval: TimeInterval = 60 * 15
+}
+/// A class that provides a convenient interface for VPN tunnels configuration, manipulation and
+/// monitoring.
+final class TunnelManager: TunnelManagerStateDelegate {
/// Operation categories
private enum OperationCategory {
static let manageTunnelProvider = "TunnelManager.manageTunnelProvider"
@@ -52,12 +68,16 @@ class TunnelManager: TunnelManagerStateDelegate
private var privateKeyRotationTimer: DispatchSourceTimer?
private var isRunningPeriodicPrivateKeyRotation = false
+ private var tunnelStatusPollTimer: DispatchSourceTimer?
+ private var isPolling = false
+ private var lastConnectingDate: Date?
+
var tunnelInfo: TunnelInfo? {
return state.tunnelInfo
}
var tunnelState: TunnelState {
- return state.tunnelState
+ return state.tunnelStatus.state
}
private init(restClient: REST.Client) {
@@ -79,7 +99,7 @@ class TunnelManager: TunnelManagerStateDelegate
stateQueue.async {
guard !self.isRunningPeriodicPrivateKeyRotation else { return }
- self.logger.debug("Start periodic private key rotation")
+ self.logger.debug("Start periodic private key rotation.")
self.isRunningPeriodicPrivateKeyRotation = true
@@ -91,7 +111,7 @@ class TunnelManager: TunnelManagerStateDelegate
stateQueue.async {
guard self.isRunningPeriodicPrivateKeyRotation else { return }
- self.logger.debug("Stop periodic private key rotation")
+ self.logger.debug("Stop periodic private key rotation.")
self.isRunningPeriodicPrivateKeyRotation = false
@@ -107,7 +127,7 @@ class TunnelManager: TunnelManagerStateDelegate
if let tunnelInfo = self.state.tunnelInfo {
let creationDate = tunnelInfo.tunnelSettings.interface.privateKey.creationDate
- let scheduleDate = Date(timeInterval: Self.privateKeyRotationInterval, since: creationDate)
+ let scheduleDate = Date(timeInterval: TunnelManagerConfiguration.privateKeyRotationInterval, since: creationDate)
schedulePrivateKeyRotationTimer(scheduleDate)
} else {
@@ -259,6 +279,11 @@ class TunnelManager: TunnelManagerStateDelegate
self.logger.error(chainedError: error, message: "Failed to reconnect the tunnel.")
}
+ // Refresh tunnel status since reasserting may not be lowered until the tunnel is fully
+ // connected.
+ self.logger.debug("Refresh tunnel status due to reconnect.")
+ self.refreshTunnelStatus()
+
DispatchQueue.main.async {
completionHandler?()
}
@@ -358,7 +383,7 @@ class TunnelManager: TunnelManagerStateDelegate
queue: stateQueue,
state: state,
restClient: restClient,
- rotationInterval: Self.privateKeyRotationInterval) { [weak self] completion in
+ rotationInterval: TunnelManagerConfiguration.privateKeyRotationInterval) { [weak self] completion in
guard let self = self else { return }
dispatchPrecondition(condition: .onQueue(self.stateQueue))
@@ -450,12 +475,23 @@ class TunnelManager: TunnelManagerStateDelegate
}
}
- func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelState newTunnelState: TunnelState) {
- logger.info("Set tunnel state: \(newTunnelState)")
+ func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelStatus newTunnelStatus: TunnelStatus) {
+ logger.info("Status: \(newTunnelStatus).")
+
+ switch newTunnelStatus.state {
+ case .connecting, .reconnecting:
+ // Start polling tunnel status to keep the relay information up to date
+ // while the tunnel process is trying to connect.
+ startPollingTunnelStatus(connectingDate: newTunnelStatus.connectingDate)
+
+ case .pendingReconnect, .connected, .disconnecting, .disconnected:
+ // Stop polling tunnel status once connection moved to final state.
+ cancelPollingTunnelStatus()
+ }
DispatchQueue.main.async {
self.observerList.forEach { (observer) in
- observer.tunnelManager(self, didUpdateTunnelState: newTunnelState)
+ observer.tunnelManager(self, didUpdateTunnelState: newTunnelStatus.state)
}
}
}
@@ -472,7 +508,8 @@ class TunnelManager: TunnelManagerStateDelegate
// Update the existing state
if shouldRefreshTunnelState {
- updateTunnelState()
+ logger.debug("Refresh tunnel status for new tunnel.")
+ refreshTunnelStatus()
}
}
@@ -482,7 +519,10 @@ class TunnelManager: TunnelManagerStateDelegate
unsubscribeVPNStatusObserver()
statusObserver = tunnel.addBlockObserver(queue: stateQueue) { [weak self] tunnel, status in
- self?.updateTunnelState()
+ guard let self = self else { return }
+
+ self.logger.debug("VPN connection status changed to \(status).")
+ self.updateTunnelStatus(status)
}
}
@@ -491,14 +531,19 @@ class TunnelManager: TunnelManagerStateDelegate
statusObserver = nil
}
- /// Update `TunnelState` from `NEVPNStatus`.
- /// Collects the `TunnelConnectionInfo` from the tunnel via IPC if needed before assigning the `tunnelState`
- private func updateTunnelState() {
+ private func refreshTunnelStatus() {
dispatchPrecondition(condition: .onQueue(stateQueue))
- guard let connectionStatus = self.state.tunnel?.status else { return }
+ if let connectionStatus = self.state.tunnel?.status {
+ updateTunnelStatus(connectionStatus)
+ }
+ }
- logger.debug("VPN status changed to \(connectionStatus)")
+ /// Update `TunnelStatus` from `NEVPNStatus`.
+ /// Collects the `PacketTunnelStatus` from the tunnel via IPC if needed before assigning
+ /// the `tunnelStatus`.
+ private func updateTunnelStatus(_ connectionStatus: NEVPNStatus) {
+ dispatchPrecondition(condition: .onQueue(stateQueue))
let operation = MapConnectionStatusOperation(queue: stateQueue, state: state, connectionStatus: connectionStatus) { [weak self] in
guard let self = self else { return }
@@ -519,8 +564,8 @@ class TunnelManager: TunnelManagerStateDelegate
@objc private func applicationDidBecomeActive() {
stateQueue.async {
- // Refresh tunnel state when application becomes active.
- self.updateTunnelState()
+ self.logger.debug("Refresh tunnel status due to application becoming active.")
+ self.refreshTunnelStatus()
}
}
@@ -592,6 +637,79 @@ class TunnelManager: TunnelManagerStateDelegate
operationQueue.addOperation(operation)
}
+ // MARK: - Tunnel status polling.
+
+ private func computeNextPollDateAndRepeatInterval(connectingDate: Date?) -> (Date, TimeInterval) {
+ let delay, repeating: TimeInterval
+ let fireDate: Date
+
+ if let connectingDate = connectingDate {
+ // Compute the schedule date for timer relative to when the packet tunnel started
+ // connecting.
+ delay = TunnelManagerConfiguration.tunnelStatusLongPollDelay
+ repeating = TunnelManagerConfiguration.tunnelStatusLongPollInterval
+
+ // Compute the time elapsed since connecting date.
+ let elapsed = max(0, Date().timeIntervalSince(connectingDate))
+
+ // Compute how many times the timer has fired so far.
+ let fireCount = floor(elapsed / repeating)
+
+ // Compute when the timer will fire next time.
+ let nextDelta = (fireCount + 1) * repeating
+
+ // Compute the fire date adding extra delay to account for leeway.
+ fireDate = connectingDate.addingTimeInterval(nextDelta + delay)
+ } else {
+ // Do quick polling until it's known when the packet tunnel started connecting.
+ delay = TunnelManagerConfiguration.tunnelStatusQuickPollDelay
+ repeating = TunnelManagerConfiguration.tunnelStatusQuickPollInterval
+
+ fireDate = Date(timeIntervalSinceNow: delay)
+ }
+
+ return (fireDate, repeating)
+ }
+
+ private func startPollingTunnelStatus(connectingDate: Date?) {
+ guard lastConnectingDate != connectingDate || !isPolling else { return }
+
+ lastConnectingDate = connectingDate
+ isPolling = true
+
+ let (fireDate, repeating) = computeNextPollDateAndRepeatInterval(connectingDate: connectingDate)
+ logger.debug("Start polling tunnel status at \(fireDate.logFormatDate()) every \(repeating) second(s).")
+
+ let timer = DispatchSource.makeTimerSource(queue: stateQueue)
+ timer.setEventHandler { [weak self] in
+ guard let self = self else { return }
+
+ self.logger.debug("Refresh tunnel status (poll).")
+ self.refreshTunnelStatus()
+ }
+
+ timer.schedule(
+ wallDeadline: .now() + fireDate.timeIntervalSinceNow,
+ repeating: repeating
+ )
+
+ timer.resume()
+
+ tunnelStatusPollTimer?.cancel()
+ tunnelStatusPollTimer = timer
+ }
+
+ private func cancelPollingTunnelStatus() {
+ guard isPolling else { return }
+
+ logger.debug("Cancel tunnel status polling.")
+
+ tunnelStatusPollTimer?.cancel()
+ tunnelStatusPollTimer = nil
+ lastConnectingDate = nil
+ isPolling = false
+ }
+
}
extension TunnelManager {
@@ -638,7 +756,7 @@ extension TunnelManager {
func scheduleBackgroundTask() -> Result<(), TunnelManager.Error> {
if let tunnelInfo = self.state.tunnelInfo {
let creationDate = tunnelInfo.tunnelSettings.interface.privateKey.creationDate
- let beginDate = Date(timeInterval: Self.privateKeyRotationInterval, since: creationDate)
+ let beginDate = Date(timeInterval: TunnelManagerConfiguration.privateKeyRotationInterval, since: creationDate)
return submitBackgroundTask(at: beginDate)
} else {
@@ -704,17 +822,17 @@ extension TunnelManager {
} else {
logger.debug("Private key rotation was cancelled")
- return Date(timeIntervalSinceNow: Self.privateKeyRotationFailureRetryInterval)
+ return Date(timeIntervalSinceNow: TunnelManagerConfiguration.privateKeyRotationFailureRetryInterval)
}
}
fileprivate func nextScheduleDate(_ result: KeyRotationResult) -> Date {
switch result {
case .finished:
- return Date(timeIntervalSinceNow: Self.privateKeyRotationInterval)
+ return Date(timeIntervalSinceNow: TunnelManagerConfiguration.privateKeyRotationInterval)
case .throttled(let lastKeyCreationDate):
- return Date(timeInterval: Self.privateKeyRotationInterval, since: lastKeyCreationDate)
+ return Date(timeInterval: TunnelManagerConfiguration.privateKeyRotationInterval, since: lastKeyCreationDate)
}
}
@@ -729,7 +847,7 @@ extension TunnelManager {
return nil
default:
- return Date(timeIntervalSinceNow: Self.privateKeyRotationFailureRetryInterval)
+ return Date(timeIntervalSinceNow: TunnelManagerConfiguration.privateKeyRotationFailureRetryInterval)
}
}
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift
index 0468d835a2..3802390376 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift
@@ -11,7 +11,7 @@ import NetworkExtension
protocol TunnelManagerStateDelegate: AnyObject {
func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelInfo newTunnelInfo: TunnelInfo?)
- func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelState newTunnelState: TunnelState)
+ func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelStatus newTunnelStatus: TunnelStatus)
func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelProvider newTunnelObject: Tunnel?, shouldRefreshTunnelState: Bool)
}
@@ -25,7 +25,11 @@ extension TunnelManager {
private var _tunnelInfo: TunnelInfo?
private var _tunnelObject: Tunnel?
- private var _tunnelState: TunnelState = .disconnected
+ private var _tunnelStatus = TunnelStatus(
+ isNetworkReachable: false,
+ connectingDate: nil,
+ state: .disconnected
+ )
var tunnelInfo: TunnelInfo? {
get {
@@ -50,18 +54,18 @@ extension TunnelManager {
}
}
- var tunnelState: TunnelState {
+ var tunnelStatus: TunnelStatus {
get {
return performBlock {
- return _tunnelState
+ return _tunnelStatus
}
}
set {
performBlock {
- if _tunnelState != newValue {
- _tunnelState = newValue
+ if _tunnelStatus != newValue {
+ _tunnelStatus = newValue
- delegate?.tunnelManagerState(self, didChangeTunnelState: newValue)
+ delegate?.tunnelManagerState(self, didChangeTunnelStatus: newValue)
}
}
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift
index b016144dd3..bec15a9044 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelState.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift
@@ -8,16 +8,61 @@
import Foundation
-/// A enum that describes the tunnel state
+/// A struct describing the tunnel status.
+struct TunnelStatus: Equatable, CustomStringConvertible {
+ /// Whether netowork is reachable.
+ var isNetworkReachable: Bool
+
+ /// When the packet tunnel started connecting.
+ var connectingDate: Date?
+
+ /// Tunnel state.
+ var state: TunnelState
+
+ var description: String {
+ var s = "\(state), network "
+
+ if isNetworkReachable {
+ s += "reachable"
+ } else {
+ s += "unreachable"
+ }
+
+ if let connectingDate = connectingDate {
+ s += ", started connecting at \(connectingDate.logFormatDate())"
+ }
+
+ return s
+ }
+
+ /// Updates the tunnel status from packet tunnel status, mapping relay to tunnel state.
+ mutating func update(from packetTunnelStatus: PacketTunnelStatus, mappingRelayToState mapper: (PacketTunnelRelay?) -> TunnelState?) {
+ isNetworkReachable = packetTunnelStatus.isNetworkReachable
+ connectingDate = packetTunnelStatus.connectingDate
+
+ if let newState = mapper(packetTunnelStatus.tunnelRelay) {
+ state = newState
+ }
+ }
+
+ /// Resets all fields to their defaults and assigns the next tunnel state.
+ mutating func reset(to newState: TunnelState) {
+ isNetworkReachable = true
+ connectingDate = nil
+ state = newState
+ }
+}
+
+/// An enum that describes the tunnel state.
enum TunnelState: Equatable, CustomStringConvertible {
/// Pending reconnect after disconnect.
case pendingReconnect
- /// Connecting the tunnel. Contains the pending action carried over from disconnected state.
- case connecting(TunnelConnectionInfo?)
+ /// Connecting the tunnel.
+ case connecting(_ relay: PacketTunnelRelay?)
/// Connected the tunnel
- case connected(TunnelConnectionInfo)
+ case connected(PacketTunnelRelay)
/// Disconnecting the tunnel
case disconnecting(ActionAfterDisconnect)
@@ -27,26 +72,26 @@ enum TunnelState: Equatable, CustomStringConvertible {
/// Reconnecting the tunnel. Normally this state appears in response to changing the
/// relay constraints and asking the running tunnel to reload the configuration.
- case reconnecting(TunnelConnectionInfo)
+ case reconnecting(_ relay: PacketTunnelRelay)
var description: String {
switch self {
case .pendingReconnect:
return "pending reconnect after disconnect"
- case .connecting(let connectionInfo):
- if let connectionInfo = connectionInfo {
- return "connecting to \(connectionInfo.hostname)"
+ case .connecting(let tunnelRelay):
+ if let tunnelRelay = tunnelRelay {
+ return "connecting to \(tunnelRelay.hostname)"
} else {
return "connecting, fetching relay"
}
- case .connected(let connectionInfo):
- return "connected to \(connectionInfo.hostname)"
+ case .connected(let tunnelRelay):
+ return "connected to \(tunnelRelay.hostname)"
case .disconnecting(let actionAfterDisconnect):
return "disconnecting and then \(actionAfterDisconnect)"
case .disconnected:
return "disconnected"
- case .reconnecting(let connectionInfo):
- return "reconnecting to \(connectionInfo.hostname)"
+ case .reconnecting(let tunnelRelay):
+ return "reconnecting to \(tunnelRelay.hostname)"
}
}
}
diff --git a/ios/PacketTunnel/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider.swift
index 9c67b084a9..61bc7a9cd4 100644
--- a/ios/PacketTunnel/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider.swift
@@ -41,16 +41,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
/// Tunnel monitor.
private var tunnelMonitor: TunnelMonitor!
- /// Tunnel connection info.
- private var tunnelConnectionInfo: TunnelConnectionInfo? {
- didSet {
- if let tunnelConnectionInfo = tunnelConnectionInfo {
- self.providerLogger.debug("Set tunnel relay to \(tunnelConnectionInfo.hostname).")
- } else {
- self.providerLogger.debug("Unset tunnel relay.")
- }
- }
- }
+ /// Tunnel status.
+ private var tunnelStatus = PacketTunnelStatus(
+ isNetworkReachable: true,
+ connectingDate: nil,
+ tunnelRelay: nil
+ )
override init() {
let pid = ProcessInfo.processInfo.processIdentifier
@@ -85,7 +81,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
switch appSelectorResult {
case .some(let selectorResult):
- providerLogger.debug("Start the tunnel via app, connect to \(selectorResult.tunnelConnectionInfo.hostname).")
+ providerLogger.debug("Start the tunnel via app, connect to \(selectorResult.relay.hostname).")
case .none:
if tunnelOptions.isOnDemand() {
@@ -114,9 +110,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
return
}
- // Set tunnel connection info.
+ // Set tunnel status.
dispatchQueue.async {
- self.tunnelConnectionInfo = tunnelConfiguration.selectorResult.tunnelConnectionInfo
+ let tunnelRelay = tunnelConfiguration.selectorResult.packetTunnelRelay
+ self.tunnelStatus.tunnelRelay = tunnelRelay
+ self.providerLogger.debug("Set tunnel relay to \(tunnelRelay.hostname).")
}
// Start tunnel.
@@ -142,7 +140,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
// Start tunnel monitor.
let gatewayAddress = tunnelConfiguration.selectorResult.endpoint.ipv4Gateway
- self.tunnelMonitor.start(address: gatewayAddress)
+
+ self.startTunnelMonitor(gatewayAddress: gatewayAddress)
}
}
}
@@ -203,10 +202,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
completionHandler?(nil)
- case .tunnelConnectionInfo:
+ case .getTunnelStatus:
var response: Data?
do {
- response = try TunnelIPC.Coding.encodeResponse(self.tunnelConnectionInfo)
+ response = try TunnelIPC.Coding.encodeResponse(self.tunnelStatus)
} catch {
self.providerLogger.error(chainedError: AnyChainedError(error), message: "Failed to encode the app message response for \(request)")
}
@@ -232,6 +231,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
providerLogger.debug("Connection established.")
+ tunnelStatus.connectingDate = nil
+
startTunnelCompletionHandler?(nil)
startTunnelCompletionHandler = nil
@@ -272,8 +273,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
return
}
- // Set tunnel connection info.
- self.tunnelConnectionInfo = tunnelConfiguration.selectorResult.tunnelConnectionInfo
+ // Update tunnel status.
+ let tunnelRelay = tunnelConfiguration.selectorResult.packetTunnelRelay
+ tunnelStatus.tunnelRelay = tunnelRelay
+ providerLogger.debug("Set tunnel relay to \(tunnelRelay.hostname).")
// Update WireGuard configuration.
adapter.update(tunnelConfiguration: tunnelConfiguration.wgTunnelConfig) { error in
@@ -285,6 +288,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
}
}
+ func tunnelMonitor(_ tunnelMonitor: TunnelMonitor, networkReachabilityStatusDidChange isNetworkReachable: Bool) {
+ tunnelStatus.isNetworkReachable = isNetworkReachable
+
+ // Adjust the start reconnect date if tunnel monitor re-started pinging in response to
+ // network connectivity coming back up.
+ if let startDate = tunnelMonitor.startDate {
+ tunnelStatus.connectingDate = startDate
+ }
+ }
+
// MARK: - Private
private func makeConfiguration(_ appSelectorResult: RelaySelectorResult? = nil) -> Result<PacketTunnelConfiguration, PacketTunnelProviderError> {
@@ -335,10 +348,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
return
}
- // Set tunnel connection info.
- let tunnelConnectionInfo = tunnelConfiguration.selectorResult.tunnelConnectionInfo
- let oldTunnelConnectionInfo = self.tunnelConnectionInfo
- self.tunnelConnectionInfo = tunnelConnectionInfo
+ // Copy old relay.
+ let oldTunnelRelay = tunnelStatus.tunnelRelay
+ let newTunnelRelay = tunnelConfiguration.selectorResult.packetTunnelRelay
+
+ // Update tunnel status.
+ tunnelStatus.tunnelRelay = newTunnelRelay
+ tunnelStatus.connectingDate = nil
+
+ providerLogger.debug("Set tunnel relay to \(newTunnelRelay.hostname).")
// Raise reasserting flag, but only if tunnel has already moved to connected state once.
// Otherwise keep the app in connecting state until it manages to establish the very first
@@ -355,8 +373,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
// Call completion handler immediately on error to update adapter configuration.
if let error = error {
- // Revert to previously used tunnel connection info.
- self.tunnelConnectionInfo = oldTunnelConnectionInfo
+ // Revert to previously used tunnel relay.
+ self.tunnelStatus.tunnelRelay = oldTunnelRelay
+ self.providerLogger.debug("Reset tunnel relay to \(oldTunnelRelay?.hostname ?? "none").")
// Lower the reasserting flag.
if self.isConnected {
@@ -381,12 +400,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider, TunnelMonitorDelegate {
// Restart tunnel monitor.
let gatewayAddress = tunnelConfiguration.selectorResult.endpoint.ipv4Gateway
- self.tunnelMonitor.start(address: gatewayAddress)
+
+ self.startTunnelMonitor(gatewayAddress: gatewayAddress)
}
}
}
}
+ private func startTunnelMonitor(gatewayAddress: IPv4Address) {
+ tunnelMonitor.start(address: gatewayAddress)
+
+ // Mark when the tunnel started monitoring connection.
+ tunnelStatus.connectingDate = tunnelMonitor.startDate
+ }
+
/// Load relay cache with potential networking to refresh the cache and pick the relay for the
/// given relay constraints.
private class func selectRelayEndpoint(relayConstraints: RelayConstraints) -> Result<RelaySelectorResult, PacketTunnelProviderError> {