From 1b434623ccb0a48fd59396e1ed837fda60615db1 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Wed, 8 May 2024 00:59:26 -0400 Subject: [PATCH 01/14] Add known failure store --- .../Diagnostics/NetworkProtectionError.swift | 7 ++- .../ExtensionMessage/ExtensionRequest.swift | 1 + .../Networking/NetworkProtectionClient.swift | 6 +- .../NetworkProtectionNotification.swift | 1 + .../PacketTunnelProvider.swift | 2 + .../Status/KnownFailure.swift | 38 +++++++++++++ .../KnownFailureObserver.swift | 26 +++++++++ ...erverThroughDistributedNotifications.swift | 56 +++++++++++++++++++ .../NetworkProtectionStatusReporter.swift | 4 ++ 9 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 Sources/NetworkProtection/Status/KnownFailure.swift create mode 100644 Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserver.swift create mode 100644 Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift index 9f17fa922..6e2a3da4c 100644 --- a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift @@ -66,6 +66,9 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { // Subscription errors case vpnAccessRevoked + // Login item errors + case loginItemVersionMismatched + // Unhandled error case unhandledError(function: String, line: Int, error: Error) @@ -94,6 +97,7 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { case .failedToParseRedeemResponse: return 111 case .invalidAuthToken: return 112 case .serverListInconsistency: return 113 + case .loginItemVersionMismatched: return 114 // 200+ - Keychain errors case .failedToCastKeychainValueToData: return 300 case .keychainReadError: return 201 @@ -138,7 +142,8 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { .wireGuardDnsResolution, .startWireGuardBackend, .noAuthTokenFound, - .vpnAccessRevoked: + .vpnAccessRevoked, + .loginItemVersionMismatched: return [:] case .failedToFetchServerList(let error), .failedToFetchRegisteredServers(let error), diff --git a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift index e4f95b4b1..dad851aa7 100644 --- a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift +++ b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift @@ -23,6 +23,7 @@ public enum DebugCommand: Codable { case removeSystemExtension case removeVPNConfiguration case sendTestNotification + case simulateKnownFailure case disableConnectOnDemandAndShutDown } diff --git a/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift b/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift index 2a86436a2..91de28df2 100644 --- a/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift +++ b/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift @@ -41,6 +41,7 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case failedToParseRedeemResponse(Error) case invalidAuthToken case accessDenied + case loginItemVersionMismatched var networkProtectionError: NetworkProtectionError { switch self { @@ -58,6 +59,7 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case .failedToParseRedeemResponse(let error): return .failedToParseRedeemResponse(error) case .invalidAuthToken: return .invalidAuthToken case .accessDenied: return .vpnAccessRevoked + case .loginItemVersionMismatched: return .loginItemVersionMismatched } } @@ -77,6 +79,7 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case .failedToParseRedeemResponse: return 11 case .invalidAuthToken: return 12 case .accessDenied: return 13 + case .loginItemVersionMismatched: return 14 } } @@ -96,7 +99,8 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC .invalidInviteCode, .failedToRetrieveAuthToken, .invalidAuthToken, - .accessDenied: + .accessDenied, + .loginItemVersionMismatched: return [:] } } diff --git a/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift b/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift index 829296ff5..86dedc1cc 100644 --- a/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift +++ b/Sources/NetworkProtection/Notifications/NetworkProtectionNotification.swift @@ -116,6 +116,7 @@ public enum NetworkProtectionNotification: String { // Error Events case tunnelErrorChanged case controllerErrorChanged + case knownFailureUpdated // New Status Observer case requestStatusUpdate diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 3c3c12ecc..7fc3f0af2 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -1021,6 +1021,8 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { handleExpireRegistrationKey(completionHandler: completionHandler) case .sendTestNotification: handleSendTestNotification(completionHandler: completionHandler) + case .simulateKnownFailure: + completionHandler?(nil) case .disableConnectOnDemandAndShutDown: Task { [weak self] in await self?.attemptShutdown { diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift new file mode 100644 index 000000000..0069a5547 --- /dev/null +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -0,0 +1,38 @@ +// +// KnownFailure.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objc +final public class KnownFailure: NSObject, Codable { + public let domain: String + public let code: Int + public let localizedDescription: String + + public init?(_ error: Error?) { + guard let nsError = error as? NSError else { return nil } + + domain = nsError.domain + code = nsError.code + localizedDescription = nsError.localizedDescription + } + + public override var description: String { + "Error domain=\(domain) code=\(code)\n\(localizedDescription)" + } +} diff --git a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserver.swift b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserver.swift new file mode 100644 index 000000000..b1ba8d686 --- /dev/null +++ b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserver.swift @@ -0,0 +1,26 @@ +// +// KnownFailureObserver.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import NetworkExtension + +public protocol KnownFailureObserver { + var publisher: AnyPublisher { get } + var recentValue: KnownFailure? { get } +} diff --git a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift new file mode 100644 index 000000000..40b84ca92 --- /dev/null +++ b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift @@ -0,0 +1,56 @@ +// +// KnownFailureObserver.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import NetworkExtension +import NotificationCenter + +public class KnownFailureObserverThroughDistributedNotifications: KnownFailureObserver { + public lazy var publisher = subject.eraseToAnyPublisher() + public var recentValue: KnownFailure? { + subject.value + } + + private let subject = CurrentValueSubject(nil) + + private let distributedNotificationCenter: DistributedNotificationCenter + private var cancellable: AnyCancellable? + + public init(distributedNotificationCenter: DistributedNotificationCenter = .default()) { + self.distributedNotificationCenter = distributedNotificationCenter + + start() + } + + private func start() { + cancellable = distributedNotificationCenter.publisher(for: .knownFailureUpdated).sink { [weak self] notification in + self?.handleKnownFailureUpdated(notification) + } + } + + private func handleKnownFailureUpdated(_ notification: Notification) { + if let object = notification.object as? String, + let data = object.data(using: .utf8), + let failure = try? JSONDecoder().decode(KnownFailure.self, from: data) { + subject.send(failure) + } else { + subject.send(nil) + } + } +} diff --git a/Sources/NetworkProtection/Status/NetworkProtectionStatusReporter.swift b/Sources/NetworkProtection/Status/NetworkProtectionStatusReporter.swift index a83e09891..f030141a4 100644 --- a/Sources/NetworkProtection/Status/NetworkProtectionStatusReporter.swift +++ b/Sources/NetworkProtection/Status/NetworkProtectionStatusReporter.swift @@ -29,6 +29,7 @@ public protocol NetworkProtectionStatusReporter { var connectivityIssuesObserver: ConnectivityIssueObserver { get } var controllerErrorMessageObserver: ControllerErrorMesssageObserver { get } var dataVolumeObserver: DataVolumeObserver { get } + var knownFailureObserver: KnownFailureObserver { get } func forceRefresh() } @@ -70,6 +71,7 @@ public final class DefaultNetworkProtectionStatusReporter: NetworkProtectionStat public let connectivityIssuesObserver: ConnectivityIssueObserver public let controllerErrorMessageObserver: ControllerErrorMesssageObserver public let dataVolumeObserver: DataVolumeObserver + public let knownFailureObserver: KnownFailureObserver // MARK: - Init & deinit @@ -79,6 +81,7 @@ public final class DefaultNetworkProtectionStatusReporter: NetworkProtectionStat connectivityIssuesObserver: ConnectivityIssueObserver, controllerErrorMessageObserver: ControllerErrorMesssageObserver, dataVolumeObserver: DataVolumeObserver, + knownFailureObserver: KnownFailureObserver, distributedNotificationCenter: DistributedNotificationCenter = .default()) { self.statusObserver = statusObserver @@ -87,6 +90,7 @@ public final class DefaultNetworkProtectionStatusReporter: NetworkProtectionStat self.connectivityIssuesObserver = connectivityIssuesObserver self.controllerErrorMessageObserver = controllerErrorMessageObserver self.dataVolumeObserver = dataVolumeObserver + self.knownFailureObserver = knownFailureObserver self.distributedNotificationCenter = distributedNotificationCenter start() From 5f7357255a7d43c25789c5248a52dc1f61214115 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Wed, 15 May 2024 23:15:40 -0400 Subject: [PATCH 02/14] Add support for ClientError 5 --- .../PacketTunnelProvider.swift | 4 ++ .../Status/KnownFailure.swift | 20 +++++-- .../NetworkProtectionKnownFailureStore.swift | 56 +++++++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 7fc3f0af2..c7fc00cb3 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -335,6 +335,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { private let bandwidthAnalyzer = NetworkProtectionConnectionBandwidthAnalyzer() private let tunnelHealth: NetworkProtectionTunnelHealthStore private let controllerErrorStore: NetworkProtectionTunnelErrorStore + private let knownFailureStore: NetworkProtectionKnownFailureStore // MARK: - Cancellables @@ -352,6 +353,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { public init(notificationsPresenter: NetworkProtectionNotificationsPresenter, tunnelHealthStore: NetworkProtectionTunnelHealthStore, controllerErrorStore: NetworkProtectionTunnelErrorStore, + knownFailureStore: NetworkProtectionKnownFailureStore = NetworkProtectionKnownFailureStore(), keychainType: KeychainType, tokenStore: NetworkProtectionTokenStore, debugEvents: EventMapping?, @@ -369,6 +371,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { self.providerEvents = providerEvents self.tunnelHealth = tunnelHealthStore self.controllerErrorStore = controllerErrorStore + self.knownFailureStore = knownFailureStore self.settings = settings self.defaults = defaults self.isSubscriptionEnabled = isSubscriptionEnabled @@ -563,6 +566,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { os_log("Tunnel startup error: %{public}@", type: .error, errorDescription) self?.controllerErrorStore.lastErrorMessage = errorDescription self?.connectionStatus = .disconnected + self?.knownFailureStore.lastKnownFailure = KnownFailure(error) providerEvents.fire(.tunnelStartAttempt(.failure(error))) completionHandler(error) diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index 0069a5547..f0af84c5d 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -25,11 +25,23 @@ final public class KnownFailure: NSObject, Codable { public let localizedDescription: String public init?(_ error: Error?) { - guard let nsError = error as? NSError else { return nil } + if let tunnelError = error as? PacketTunnelProvider.TunnelError, + case .couldNotGenerateTunnelConfiguration(let internalError) = tunnelError { + let nsError = internalError as NSError + domain = nsError.domain + code = nsError.code + localizedDescription = nsError.localizedDescription + return + } - domain = nsError.domain - code = nsError.code - localizedDescription = nsError.localizedDescription + if let nsError = error as? NSError { + domain = nsError.domain + code = nsError.code + localizedDescription = nsError.localizedDescription + return + } + + return nil } public override var description: String { diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift new file mode 100644 index 000000000..4e08dd972 --- /dev/null +++ b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift @@ -0,0 +1,56 @@ +// +// NetworkProtectionKnownFailureStore.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Common + +final public class NetworkProtectionKnownFailureStore { + private static let lastKnownFailureKey = "com.duckduckgo.NetworkProtectionKnownFailureStore.knownFailure" + private let userDefaults: UserDefaults + private let distributedNotificationCenter: DistributedNotificationCenter + + public init(userDefaults: UserDefaults = .standard, + distributedNotificationCenter: DistributedNotificationCenter = .default()) { + self.userDefaults = userDefaults + self.distributedNotificationCenter = distributedNotificationCenter + } + + public var lastKnownFailure: KnownFailure? { + get { + guard let data = userDefaults.data(forKey: Self.lastKnownFailureKey) else { return nil } + return try? JSONDecoder().decode(KnownFailure.self, from: data) + } + + set { + os_log("🟢 Known failure set to \(newValue)") + guard let data = try? JSONEncoder().encode(newValue) else { return } + userDefaults.set(data, forKey: Self.lastKnownFailureKey) + postKnownFailureUpdatedNotification(data: data) + } + } + + // MARK: - Posting Notifications + + private func postKnownFailureUpdatedNotification(data: Data?) { + let object: String? = { + guard let data else { return nil } + return String(data: data, encoding: .utf8) + }() + distributedNotificationCenter.post(.knownFailureUpdated, object: object) + } +} From 435b15666ecca8da9582746918a177abcf814156 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Wed, 15 May 2024 23:27:30 -0400 Subject: [PATCH 03/14] Fix Swiftlint --- .../KnownFailureObserverThroughDistributedNotifications.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift index 40b84ca92..32917382a 100644 --- a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift +++ b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift @@ -1,5 +1,5 @@ // -// KnownFailureObserver.swift +// KnownFailureObserverThroughDistributedNotifications.swift // // Copyright © 2024 DuckDuckGo. All rights reserved. // From f8bff07cca79ac40f844f8ebffb4965f804be350 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Thu, 16 May 2024 22:05:05 -0400 Subject: [PATCH 04/14] Move versionMismatched error out of BSK --- .../Diagnostics/NetworkProtectionError.swift | 7 +------ .../Networking/NetworkProtectionClient.swift | 6 +----- Sources/NetworkProtection/PacketTunnelProvider.swift | 8 ++++---- Sources/NetworkProtection/Status/KnownFailure.swift | 9 ++++++--- .../Storage/NetworkProtectionKnownFailureStore.swift | 1 - 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift index 6e2a3da4c..9f17fa922 100644 --- a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift @@ -66,9 +66,6 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { // Subscription errors case vpnAccessRevoked - // Login item errors - case loginItemVersionMismatched - // Unhandled error case unhandledError(function: String, line: Int, error: Error) @@ -97,7 +94,6 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { case .failedToParseRedeemResponse: return 111 case .invalidAuthToken: return 112 case .serverListInconsistency: return 113 - case .loginItemVersionMismatched: return 114 // 200+ - Keychain errors case .failedToCastKeychainValueToData: return 300 case .keychainReadError: return 201 @@ -142,8 +138,7 @@ public enum NetworkProtectionError: LocalizedError, CustomNSError { .wireGuardDnsResolution, .startWireGuardBackend, .noAuthTokenFound, - .vpnAccessRevoked, - .loginItemVersionMismatched: + .vpnAccessRevoked: return [:] case .failedToFetchServerList(let error), .failedToFetchRegisteredServers(let error), diff --git a/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift b/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift index 91de28df2..2a86436a2 100644 --- a/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift +++ b/Sources/NetworkProtection/Networking/NetworkProtectionClient.swift @@ -41,7 +41,6 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case failedToParseRedeemResponse(Error) case invalidAuthToken case accessDenied - case loginItemVersionMismatched var networkProtectionError: NetworkProtectionError { switch self { @@ -59,7 +58,6 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case .failedToParseRedeemResponse(let error): return .failedToParseRedeemResponse(error) case .invalidAuthToken: return .invalidAuthToken case .accessDenied: return .vpnAccessRevoked - case .loginItemVersionMismatched: return .loginItemVersionMismatched } } @@ -79,7 +77,6 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC case .failedToParseRedeemResponse: return 11 case .invalidAuthToken: return 12 case .accessDenied: return 13 - case .loginItemVersionMismatched: return 14 } } @@ -99,8 +96,7 @@ public enum NetworkProtectionClientError: CustomNSError, NetworkProtectionErrorC .invalidInviteCode, .failedToRetrieveAuthToken, .invalidAuthToken, - .accessDenied, - .loginItemVersionMismatched: + .accessDenied: return [:] } } diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index c7fc00cb3..9332dd2f2 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -63,7 +63,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { // MARK: - Error Handling - enum TunnelError: LocalizedError, CustomNSError { + public enum TunnelError: LocalizedError, CustomNSError { // Tunnel Setup Errors - 0+ case startingTunnelWithoutAuthToken case couldNotGenerateTunnelConfiguration(internalError: Error) @@ -72,7 +72,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { // Subscription Errors - 100+ case vpnAccessRevoked - var errorDescription: String? { + public var errorDescription: String? { switch self { case .startingTunnelWithoutAuthToken: return "Missing auth token at startup" @@ -85,7 +85,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } } - var errorCode: Int { + public var errorCode: Int { switch self { // Tunnel Setup Errors - 0+ case .startingTunnelWithoutAuthToken: return 0 @@ -96,7 +96,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { } } - var errorUserInfo: [String: Any] { + public var errorUserInfo: [String: Any] { switch self { case .startingTunnelWithoutAuthToken, .simulateTunnelFailureError, diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index f0af84c5d..b03299071 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -18,6 +18,10 @@ import Foundation +public protocol InternalErrorWrapping: Error { + var internalError: Error? { get } +} + @objc final public class KnownFailure: NSObject, Codable { public let domain: String @@ -25,9 +29,8 @@ final public class KnownFailure: NSObject, Codable { public let localizedDescription: String public init?(_ error: Error?) { - if let tunnelError = error as? PacketTunnelProvider.TunnelError, - case .couldNotGenerateTunnelConfiguration(let internalError) = tunnelError { - let nsError = internalError as NSError + if let error = error as? InternalErrorWrapping, + let nsError = error.internalError as? NSError { domain = nsError.domain code = nsError.code localizedDescription = nsError.localizedDescription diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift index 4e08dd972..787f8b29c 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift @@ -37,7 +37,6 @@ final public class NetworkProtectionKnownFailureStore { } set { - os_log("🟢 Known failure set to \(newValue)") guard let data = try? JSONEncoder().encode(newValue) else { return } userDefaults.set(data, forKey: Self.lastKnownFailureKey) postKnownFailureUpdatedNotification(data: data) From e200aaba0a252dad613dfa18288e45502220cc6a Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 17 May 2024 12:19:07 -0400 Subject: [PATCH 05/14] Fix lastKnownFailure setting --- .../ExtensionMessage/ExtensionRequest.swift | 2 +- .../Storage/NetworkProtectionKnownFailureStore.swift | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift index f6972f173..c35142370 100644 --- a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift +++ b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift @@ -23,7 +23,7 @@ public enum VPNCommand: Codable { case removeSystemExtension case removeVPNConfiguration case sendTestNotification - case simulateKnownFailure + case simulateKnownFailure(String, Int) case uninstallVPN case disableConnectOnDemandAndShutDown } diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift index 787f8b29c..89728e815 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift @@ -37,15 +37,19 @@ final public class NetworkProtectionKnownFailureStore { } set { - guard let data = try? JSONEncoder().encode(newValue) else { return } - userDefaults.set(data, forKey: Self.lastKnownFailureKey) - postKnownFailureUpdatedNotification(data: data) + if let newValue, let data = try? JSONEncoder().encode(newValue) { + userDefaults.set(data, forKey: Self.lastKnownFailureKey) + postKnownFailureUpdatedNotification(data: data) + } else { + userDefaults.removeObject(forKey: Self.lastKnownFailureKey) + postKnownFailureUpdatedNotification() + } } } // MARK: - Posting Notifications - private func postKnownFailureUpdatedNotification(data: Data?) { + private func postKnownFailureUpdatedNotification(data: Data? = nil) { let object: String? = { guard let data else { return nil } return String(data: data, encoding: .utf8) From 26700ba1741861ccf6710817ffb933001ac67b65 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 17 May 2024 13:44:00 -0400 Subject: [PATCH 06/14] Exclude iOS --- .../KnownFailureObserverThroughDistributedNotifications.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift index 32917382a..5ea43c300 100644 --- a/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift +++ b/Sources/NetworkProtection/Status/KnownFailureObserver/KnownFailureObserverThroughDistributedNotifications.swift @@ -16,6 +16,8 @@ // limitations under the License. // +#if os(macOS) + import Combine import Foundation import NetworkExtension @@ -54,3 +56,5 @@ public class KnownFailureObserverThroughDistributedNotifications: KnownFailureOb } } } + +#endif From 20ed11ceef9874169138bc4f3334c8d957c64dec Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 17 May 2024 14:01:30 -0400 Subject: [PATCH 07/14] Platform specific update --- .../NetworkProtectionKnownFailureStore.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift index 89728e815..834d99040 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift @@ -22,6 +22,8 @@ import Common final public class NetworkProtectionKnownFailureStore { private static let lastKnownFailureKey = "com.duckduckgo.NetworkProtectionKnownFailureStore.knownFailure" private let userDefaults: UserDefaults + +#if os(macOS) private let distributedNotificationCenter: DistributedNotificationCenter public init(userDefaults: UserDefaults = .standard, @@ -29,6 +31,11 @@ final public class NetworkProtectionKnownFailureStore { self.userDefaults = userDefaults self.distributedNotificationCenter = distributedNotificationCenter } +#else + public init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults + } +#endif public var lastKnownFailure: KnownFailure? { get { @@ -39,16 +46,21 @@ final public class NetworkProtectionKnownFailureStore { set { if let newValue, let data = try? JSONEncoder().encode(newValue) { userDefaults.set(data, forKey: Self.lastKnownFailureKey) +#if os(macOS) postKnownFailureUpdatedNotification(data: data) +#endif } else { userDefaults.removeObject(forKey: Self.lastKnownFailureKey) +#if os(macOS) postKnownFailureUpdatedNotification() +#endif } } } // MARK: - Posting Notifications +#if os(macOS) private func postKnownFailureUpdatedNotification(data: Data? = nil) { let object: String? = { guard let data else { return nil } @@ -56,4 +68,5 @@ final public class NetworkProtectionKnownFailureStore { }() distributedNotificationCenter.post(.knownFailureUpdated, object: object) } +#endif } From d2a9125b8b5008dc826fd26393d3da597329ffd6 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 17 May 2024 17:34:43 -0400 Subject: [PATCH 08/14] Fix unit tests --- .../Status/MockKnownFailureObserver.swift | 29 +++++++++++++++++++ .../MockNetworkProtectionStatusReporter.swift | 3 ++ 2 files changed, 32 insertions(+) create mode 100644 Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift diff --git a/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift b/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift new file mode 100644 index 000000000..9cfb9888d --- /dev/null +++ b/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift @@ -0,0 +1,29 @@ +// +// MockKnownFailureObserver.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import NetworkProtection + +public final class MockKnownFailureObserver: KnownFailureObserver { + public let subject = CurrentValueSubject(nil) + public lazy var publisher = subject.eraseToAnyPublisher() + public var recentValue: KnownFailure? { + subject.value + } +} diff --git a/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift b/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift index 9df4f1460..4656e0597 100644 --- a/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift +++ b/Sources/NetworkProtectionTestUtils/Status/MockNetworkProtectionStatusReporter.swift @@ -35,6 +35,7 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR public let connectivityIssuesObserver: ConnectivityIssueObserver public let controllerErrorMessageObserver: ControllerErrorMesssageObserver public let dataVolumeObserver: DataVolumeObserver + public let knownFailureObserver: KnownFailureObserver // MARK: - Init & deinit @@ -44,6 +45,7 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR connectivityIssuesObserver: ConnectivityIssueObserver = MockConnectivityIssueObserver(), controllerErrorMessageObserver: ControllerErrorMesssageObserver = MockControllerErrorMesssageObserver(), dataVolumeObserver: DataVolumeObserver = MockDataVolumeObserver(), + knownFailureObserver: KnownFailureObserver = MockKnownFailureObserver(), distributedNotificationCenter: DistributedNotificationCenter = .default()) { self.statusObserver = statusObserver @@ -51,6 +53,7 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR self.connectionErrorObserver = connectionErrorObserver self.connectivityIssuesObserver = connectivityIssuesObserver self.dataVolumeObserver = dataVolumeObserver + self.knownFailureObserver = knownFailureObserver self.controllerErrorMessageObserver = controllerErrorMessageObserver } From d1dafa663f32c9ccb9be98c1856a372bb522c92d Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 17 May 2024 17:51:11 -0400 Subject: [PATCH 09/14] Fix unit tests --- .../Status/MockKnownFailureObserver.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift b/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift index 9cfb9888d..5fadc4db5 100644 --- a/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift +++ b/Sources/NetworkProtectionTestUtils/Status/MockKnownFailureObserver.swift @@ -21,6 +21,7 @@ import Foundation import NetworkProtection public final class MockKnownFailureObserver: KnownFailureObserver { + public init() {} public let subject = CurrentValueSubject(nil) public lazy var publisher = subject.eraseToAnyPublisher() public var recentValue: KnownFailure? { From b94a091906039ec230516b157636f70d0cbd014d Mon Sep 17 00:00:00 2001 From: Anh Do Date: Tue, 21 May 2024 22:10:54 -0400 Subject: [PATCH 10/14] Remove localizedDescription --- Sources/NetworkProtection/Status/KnownFailure.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index b03299071..023bb0796 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -48,6 +48,6 @@ final public class KnownFailure: NSObject, Codable { } public override var description: String { - "Error domain=\(domain) code=\(code)\n\(localizedDescription)" + "Error domain=\(domain) code=\(code)" } } From 24af5727be13ee75b828b74c5d18979cbe837b7e Mon Sep 17 00:00:00 2001 From: Anh Do Date: Mon, 3 Jun 2024 10:37:21 -0400 Subject: [PATCH 11/14] Draft --- .../PacketTunnelProvider.swift | 12 +++- .../Status/KnownFailure.swift | 61 +++++++++++++------ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 5d6d16d77..6ef4f7ef0 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -63,7 +63,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { // MARK: - Error Handling - public enum TunnelError: LocalizedError, CustomNSError { + public enum TunnelError: LocalizedError, CustomNSError, SilentErrorConvertible { // Tunnel Setup Errors - 0+ case startingTunnelWithoutAuthToken case couldNotGenerateTunnelConfiguration(internalError: Error) @@ -106,6 +106,16 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { return [NSUnderlyingErrorKey: underlyingError] } } + + public var asSilentError: KnownFailure.SilentError? { + guard case .couldNotGenerateTunnelConfiguration(let internalError) = self, + let clientError = internalError as? NetworkProtectionClientError, + case .failedToFetchRegisteredServers = clientError else { + return nil + } + + return .registeredServerFetchingFailed + } } // MARK: - WireGuard diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index 023bb0796..19ecf43ef 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -18,36 +18,61 @@ import Foundation -public protocol InternalErrorWrapping: Error { - var internalError: Error? { get } +public protocol SilentErrorConvertible: Error { + var asSilentError: KnownFailure.SilentError? { get } } @objc final public class KnownFailure: NSObject, Codable { - public let domain: String - public let code: Int - public let localizedDescription: String + public enum SilentError: Int { + case operationNotPermitted + case loginItemVersionMismatched + case registeredServerFetchingFailed + } + + public let error: Int public init?(_ error: Error?) { - if let error = error as? InternalErrorWrapping, - let nsError = error.internalError as? NSError { - domain = nsError.domain - code = nsError.code - localizedDescription = nsError.localizedDescription + if let nsError = error as? NSError, nsError.domain == "SMAppServiceErrorDomain", nsError.code == 1 { + self.error = SilentError.operationNotPermitted.rawValue return } - if let nsError = error as? NSError { - domain = nsError.domain - code = nsError.code - localizedDescription = nsError.localizedDescription + if let error = error as? SilentErrorConvertible, let silentError = error.asSilentError { + self.error = silentError.rawValue return } return nil } - - public override var description: String { - "Error domain=\(domain) code=\(code)" - } } + +//@objc +//final public class KnownFailure: NSObject, Codable { +// public let domain: String +// public let code: Int +// public let localizedDescription: String +// +// public init?(_ error: Error?) { +// if let error = error as? InternalErrorWrapping, +// let nsError = error.internalError as? NSError { +// domain = nsError.domain +// code = nsError.code +// localizedDescription = nsError.localizedDescription +// return +// } +// +// if let nsError = error as? NSError { +// domain = nsError.domain +// code = nsError.code +// localizedDescription = nsError.localizedDescription +// return +// } +// +// return nil +// } +// +// public override var description: String { +// "Error domain=\(domain) code=\(code)" +// } +//} From 997877cfdda7e9ec617699f150d562ae7611fb86 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Mon, 3 Jun 2024 11:23:20 -0400 Subject: [PATCH 12/14] Remove debug command --- .../ExtensionMessage/ExtensionRequest.swift | 1 - .../PacketTunnelProvider.swift | 2 -- .../Status/KnownFailure.swift | 30 ------------------- 3 files changed, 33 deletions(-) diff --git a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift index c35142370..03f59244c 100644 --- a/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift +++ b/Sources/NetworkProtection/ExtensionMessage/ExtensionRequest.swift @@ -23,7 +23,6 @@ public enum VPNCommand: Codable { case removeSystemExtension case removeVPNConfiguration case sendTestNotification - case simulateKnownFailure(String, Int) case uninstallVPN case disableConnectOnDemandAndShutDown } diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 6ef4f7ef0..8e4c0cea2 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -1035,8 +1035,6 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { handleExpireRegistrationKey(completionHandler: completionHandler) case .sendTestNotification: handleSendTestNotification(completionHandler: completionHandler) - case .simulateKnownFailure: - completionHandler?(nil) case .disableConnectOnDemandAndShutDown: Task { [weak self] in await self?.attemptShutdown { diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index 19ecf43ef..e5dbdfc15 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -46,33 +46,3 @@ final public class KnownFailure: NSObject, Codable { return nil } } - -//@objc -//final public class KnownFailure: NSObject, Codable { -// public let domain: String -// public let code: Int -// public let localizedDescription: String -// -// public init?(_ error: Error?) { -// if let error = error as? InternalErrorWrapping, -// let nsError = error.internalError as? NSError { -// domain = nsError.domain -// code = nsError.code -// localizedDescription = nsError.localizedDescription -// return -// } -// -// if let nsError = error as? NSError { -// domain = nsError.domain -// code = nsError.code -// localizedDescription = nsError.localizedDescription -// return -// } -// -// return nil -// } -// -// public override var description: String { -// "Error domain=\(domain) code=\(code)" -// } -//} From 6f09c82e15b926a8a3d98341c339c77618aa6082 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Mon, 3 Jun 2024 11:33:08 -0400 Subject: [PATCH 13/14] Draft --- Sources/NetworkProtection/Status/KnownFailure.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/NetworkProtection/Status/KnownFailure.swift b/Sources/NetworkProtection/Status/KnownFailure.swift index e5dbdfc15..53ec740f8 100644 --- a/Sources/NetworkProtection/Status/KnownFailure.swift +++ b/Sources/NetworkProtection/Status/KnownFailure.swift @@ -24,13 +24,15 @@ public protocol SilentErrorConvertible: Error { @objc final public class KnownFailure: NSObject, Codable { - public enum SilentError: Int { + public typealias SilentErrorCode = Int + + public enum SilentError: SilentErrorCode { case operationNotPermitted case loginItemVersionMismatched case registeredServerFetchingFailed } - public let error: Int + public let error: SilentErrorCode public init?(_ error: Error?) { if let nsError = error as? NSError, nsError.domain == "SMAppServiceErrorDomain", nsError.code == 1 { From c86370381edc47a4202901f7dee90539146f799e Mon Sep 17 00:00:00 2001 From: Anh Do Date: Mon, 3 Jun 2024 12:03:49 -0400 Subject: [PATCH 14/14] Add tests and address PR comments --- .../NetworkProtectionKnownFailureStore.swift | 4 +++ .../KnownFailureTests.swift | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 Tests/NetworkProtectionTests/KnownFailureTests.swift diff --git a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift index 834d99040..294571ecf 100644 --- a/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift +++ b/Sources/NetworkProtection/Storage/NetworkProtectionKnownFailureStore.swift @@ -58,6 +58,10 @@ final public class NetworkProtectionKnownFailureStore { } } + public func reset() { + lastKnownFailure = nil + } + // MARK: - Posting Notifications #if os(macOS) diff --git a/Tests/NetworkProtectionTests/KnownFailureTests.swift b/Tests/NetworkProtectionTests/KnownFailureTests.swift new file mode 100644 index 000000000..41f06648f --- /dev/null +++ b/Tests/NetworkProtectionTests/KnownFailureTests.swift @@ -0,0 +1,36 @@ +// +// KnownFailureTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import Foundation +@testable import NetworkProtection + +final class KnownFailureTests: XCTestCase { + func testHardCodedErrorInitializer() { + let error = NSError(domain: "SMAppServiceErrorDomain", code: 1) + XCTAssertEqual(KnownFailure(error)!.error, KnownFailure.SilentError.operationNotPermitted.rawValue) + } + + func testNonHardCodedErrorInitializer() { + let internalError = NetworkProtectionClientError.failedToFetchRegisteredServers("404") + let error = PacketTunnelProvider.TunnelError.couldNotGenerateTunnelConfiguration(internalError: internalError) + XCTAssertEqual(KnownFailure(error)!.error, KnownFailure.SilentError.registeredServerFetchingFailed.rawValue) + } +} + +extension String: Error {}