summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-02-21 18:01:47 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-03-17 09:49:07 +0100
commitc74a65eadba05f661779dab41b32e2488b9cbfe7 (patch)
tree5836dd6c70e8f0e4ac8e8754e0346015ce842df3
parent92304fd9f9436209531aaa78125f843f918f7803 (diff)
downloadmullvadvpn-c74a65eadba05f661779dab41b32e2488b9cbfe7.tar.xz
mullvadvpn-c74a65eadba05f661779dab41b32e2488b9cbfe7.zip
IPC: add 5 second delay before talking to the tunnel in connecting state
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift73
1 files changed, 55 insertions, 18 deletions
diff --git a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift
index 0b74f6c737..08fdfe1dbf 100644
--- a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift
+++ b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift
@@ -12,6 +12,10 @@ import NetworkExtension
extension TunnelIPC {
struct RequestOptions {
+ /// Delay for sending IPC requests to the tunnel when in connecting state.
+ /// Used to workaround a bug when talking to the tunnel too early may cause it to freeze.
+ static let connectingStateWaitDelay: TimeInterval = 5
+
/// Wait until the tunnel transitioned from reasserting to connected state before sending
/// the request.
var waitIfReasserting: Bool
@@ -35,7 +39,8 @@ extension TunnelIPC {
private var completionHandler: CompletionHandler?
private var statusObserver: NSObjectProtocol?
- private var timeoutTimer: DispatchSourceTimer?
+ private var timeoutWork: DispatchWorkItem?
+ private var waitForConnectingStateWork: DispatchWorkItem?
init(queue: DispatchQueue,
connection: VPNConnectionProtocol,
@@ -78,7 +83,7 @@ extension TunnelIPC {
return
}
- startTimeoutTimer()
+ setTimeoutTimer(isConnectingState: false)
statusObserver = NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
@@ -100,21 +105,24 @@ extension TunnelIPC {
}
}
- private func startTimeoutTimer() {
- let timer = DispatchSource.makeTimerSource(queue: queue)
- timer.setEventHandler { [weak self] in
+ private func setTimeoutTimer(isConnectingState: Bool) {
+ let workItem = DispatchWorkItem { [weak self] in
self?.completeOperation(completion: .failure(.send(.timeout)))
}
- timer.schedule(wallDeadline: .now() + options.timeout)
- timer.activate()
+ // Cancel pending timeout work.
+ timeoutWork?.cancel()
- timeoutTimer = timer
- }
+ // Assign new timeout work.
+ timeoutWork = workItem
+
+ // Compute additional delay associated with connecting state.
+ let connectingStateWaitDelay = isConnectingState ? RequestOptions.connectingStateWaitDelay : 0
+
+ // Schedule timeout work.
+ let deadline: DispatchWallTime = .now() + options.timeout + connectingStateWaitDelay
- private func stopTimeoutTimer() {
- timeoutTimer?.cancel()
- timeoutTimer = nil
+ queue.asyncAfter(wallDeadline: deadline, execute: workItem)
}
private func handleVPNStatus(_ status: NEVPNStatus) {
@@ -127,9 +135,9 @@ extension TunnelIPC {
sendRequest()
case .connecting:
- // Sending IPC message while in connecting state may cause the tunnel process to
- // freeze for no apparent reason.
- break
+ waitForConnectingState { [weak self] in
+ self?.sendRequest()
+ }
case .reasserting:
if !options.waitIfReasserting {
@@ -144,11 +152,32 @@ extension TunnelIPC {
}
}
- private func sendRequest() {
- let session = connection as! VPNTunnelProviderSessionProtocol
+ private func waitForConnectingState(block: @escaping () -> Void) {
+ let workItem = DispatchWorkItem(block: block)
+
+ // Cancel pending work.
+ waitForConnectingStateWork?.cancel()
+ // Assign new work.
+ waitForConnectingStateWork = workItem
+
+ // Reschedule the timeout work.
+ setTimeoutTimer(isConnectingState: true)
+
+ // Schedule delayed work.
+ let deadline: DispatchWallTime = .now() + RequestOptions.connectingStateWaitDelay
+
+ queue.asyncAfter(wallDeadline: deadline, execute: workItem)
+ }
+
+ private func sendRequest() {
+ // Release status observer.
removeVPNStatusObserver()
+ // Cancel pending delayed work.
+ waitForConnectingStateWork?.cancel()
+
+ // Encode request.
let messageData: Data
do {
messageData = try TunnelIPC.Coding.encodeRequest(request)
@@ -157,7 +186,9 @@ extension TunnelIPC {
return
}
+ // Send IPC message.
do {
+ let session = connection as! VPNTunnelProviderSessionProtocol
try session.sendProviderMessage(messageData) { [weak self] responseData in
guard let self = self else { return }
@@ -173,12 +204,18 @@ extension TunnelIPC {
}
private func completeOperation(completion: OperationCompletion<Output, TunnelIPC.Error>) {
+ // Release status observer.
removeVPNStatusObserver()
- stopTimeoutTimer()
+ // Cancel pending work.
+ timeoutWork?.cancel()
+ waitForConnectingStateWork?.cancel()
+
+ // Call completion handler.
completionHandler?(completion)
completionHandler = nil
+ // Finish operation.
finish()
}
}