diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift index ec09ba5ba..98653e1c8 100644 --- a/Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionConnectionTester.swift @@ -32,7 +32,7 @@ import Common final class NetworkProtectionConnectionTester { enum Result { case connected - case reconnected + case reconnected(failureCount: Int) case disconnected(failureCount: Int) } @@ -267,9 +267,8 @@ final class NetworkProtectionConnectionTester { if failureCount == 0 { resultHandler(.connected) } else if failureCount > 0 { + resultHandler(.reconnected(failureCount: failureCount)) failureCount = 0 - - resultHandler(.reconnected) } } diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 730c044e7..1111d62f5 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -32,6 +32,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { public enum Event { case userBecameActive + case connectionTesterStatusChange(_ status: ConnectionTesterStatus, server: String) case reportConnectionAttempt(attempt: ConnectionAttempt) case tunnelStartAttempt(_ step: TunnelStartAttemptStep) case tunnelStopAttempt(_ step: TunnelStopAttemptStep) @@ -64,6 +65,16 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { case failure } + public enum ConnectionTesterStatus { + case failed(duration: Duration) + case recovered(duration: Duration, failureCount: Int) + + public enum Duration: String { + case immediate + case extended + } + } + // MARK: - Error Handling public enum TunnelError: LocalizedError, CustomNSError, SilentErrorConvertible { @@ -309,6 +320,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { // MARK: - Connection tester + private static let connectionTesterExtendedFailuresCount = 8 private var isConnectionTesterEnabled: Bool = true @MainActor @@ -316,16 +328,42 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { NetworkProtectionConnectionTester(timerQueue: timerQueue, log: .networkProtectionConnectionTesterLog) { @MainActor [weak self] result in guard let self else { return } + let serverName = lastSelectedServerInfo?.name ?? "Unknown" + switch result { case .connected: self.tunnelHealth.isHavingConnectivityIssues = false self.updateBandwidthAnalyzerAndRekeyIfExpired() - case .reconnected: + case .reconnected(let failureCount): + providerEvents.fire( + .connectionTesterStatusChange( + .recovered(duration: .immediate, failureCount: failureCount), + server: serverName)) + + if failureCount >= Self.connectionTesterExtendedFailuresCount { + providerEvents.fire( + .connectionTesterStatusChange( + .recovered(duration: .extended, failureCount: failureCount), + server: serverName)) + } + self.tunnelHealth.isHavingConnectivityIssues = false self.updateBandwidthAnalyzerAndRekeyIfExpired() case .disconnected(let failureCount): + if failureCount == 1 { + providerEvents.fire( + .connectionTesterStatusChange( + .failed(duration: .immediate), + server: serverName)) + } else if failureCount == 8 { + providerEvents.fire( + .connectionTesterStatusChange( + .failed(duration: .extended), + server: serverName)) + } + self.tunnelHealth.isHavingConnectivityIssues = true self.bandwidthAnalyzer.reset() } diff --git a/Sources/PixelKit/PixelKit+Parameters.swift b/Sources/PixelKit/PixelKit+Parameters.swift index b69227170..377e56c33 100644 --- a/Sources/PixelKit/PixelKit+Parameters.swift +++ b/Sources/PixelKit/PixelKit+Parameters.swift @@ -21,6 +21,7 @@ import Foundation public extension PixelKit { enum Parameters: Hashable { + public static let count = "count" public static let duration = "duration" public static let test = "test" public static let appVersion = "appVersion"