diff --git a/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift new file mode 100644 index 000000000..1cd93c3ad --- /dev/null +++ b/Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift @@ -0,0 +1,80 @@ +// +// NetworkProtectionError.swift +// DuckDuckGo +// +// Copyright © 2023 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 + +protocol NetworkProtectionErrorConvertible { + var networkProtectionError: NetworkProtectionError { get } +} + +public enum NetworkProtectionError: LocalizedError { + // Tunnel configuration errors + case noServerRegistrationInfo + case couldNotSelectClosestServer + case couldNotGetPeerPublicKey + case couldNotGetPeerHostName + case couldNotGetInterfaceAddressRange + + // Client errors + case failedToFetchServerList(Error?) + case failedToParseServerListResponse(Error) + case failedToEncodeRegisterKeyRequest + case failedToFetchRegisteredServers(Error?) + case failedToParseRegisteredServersResponse(Error) + case failedToEncodeRedeemRequest + case invalidInviteCode + case failedToRedeemInviteCode(Error?) + case failedToParseRedeemResponse(Error) + case invalidAuthToken + case serverListInconsistency + + // Server list store errors + case failedToEncodeServerList(Error) + case failedToDecodeServerList(Error) + case failedToWriteServerList(Error) + case noServerListFound + case couldNotCreateServerListDirectory(Error) + case failedToReadServerList(Error) + + // Keychain errors + case failedToCastKeychainValueToData(field: String) + case keychainReadError(field: String, status: Int32) + case keychainWriteError(field: String, status: Int32) + case keychainDeleteError(status: Int32) + + // Wireguard errors + case wireGuardCannotLocateTunnelFileDescriptor + case wireGuardInvalidState + case wireGuardDnsResolution + case wireGuardSetNetworkSettings(Error) + case startWireGuardBackend(Int32) + + // Auth errors + case noAuthTokenFound + + // Unhandled error + case unhandledError(function: String, line: Int, error: Error) + + public var errorDescription: String? { + // This is probably not the most elegant error to show to a user but + // it's a great way to get detailed reports for those cases we haven't + // provided good descriptions for yet. + return "NetworkProtectionError.\(String(describing: self))" + } +} diff --git a/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift b/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift new file mode 100644 index 000000000..d23d13e3e --- /dev/null +++ b/Sources/NetworkProtection/Diagnostics/WireGuardAdapterError+NetworkProtectionErrorConvertible.swift @@ -0,0 +1,37 @@ +// +// WireguardAdapterError+NetworkProtectionErrorConvertible.swift +// DuckDuckGo +// +// Copyright © 2023 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 + +extension WireGuardAdapterError: NetworkProtectionErrorConvertible { + var networkProtectionError: NetworkProtectionError { + switch self { + case .cannotLocateTunnelFileDescriptor: + return .wireGuardCannotLocateTunnelFileDescriptor + case .invalidState: + return .wireGuardInvalidState + case .dnsResolution: + return .wireGuardDnsResolution + case .setNetworkSettings(let error): + return .wireGuardSetNetworkSettings(error) + case .startWireGuardBackend(let code): + return .startWireGuardBackend(code) + } + } +} diff --git a/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift b/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift index 699bcecdf..b9c062913 100644 --- a/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift +++ b/Sources/NetworkProtection/NetworkProtectionDeviceManager.swift @@ -35,59 +35,6 @@ public protocol NetworkProtectionDeviceManagement { } -protocol NetworkProtectionErrorConvertible { - var networkProtectionError: NetworkProtectionError { get } -} - -public enum NetworkProtectionError: LocalizedError { - // Tunnel configuration errors - case noServerRegistrationInfo - case couldNotSelectClosestServer - case couldNotGetPeerPublicKey - case couldNotGetPeerHostName - case couldNotGetInterfaceAddressRange - - // Client errors - case failedToFetchServerList(Error?) - case failedToParseServerListResponse(Error) - case failedToEncodeRegisterKeyRequest - case failedToFetchRegisteredServers(Error?) - case failedToParseRegisteredServersResponse(Error) - case failedToEncodeRedeemRequest - case invalidInviteCode - case failedToRedeemInviteCode(Error?) - case failedToParseRedeemResponse(Error) - case invalidAuthToken - case serverListInconsistency - - // Server list store errors - case failedToEncodeServerList(Error) - case failedToDecodeServerList(Error) - case failedToWriteServerList(Error) - case noServerListFound - case couldNotCreateServerListDirectory(Error) - case failedToReadServerList(Error) - - // Keychain errors - case failedToCastKeychainValueToData(field: String) - case keychainReadError(field: String, status: Int32) - case keychainWriteError(field: String, status: Int32) - case keychainDeleteError(status: Int32) - - // Auth errors - case noAuthTokenFound - - // Unhandled error - case unhandledError(function: String, line: Int, error: Error) - - public var errorDescription: String? { - // This is probably not the most elegant error to show to a user but - // it's a great way to get detailed reports for those cases we haven't - // provided good descriptions for yet. - return "NetworkProtectionError.\(String(describing: self))" - } -} - public actor NetworkProtectionDeviceManager: NetworkProtectionDeviceManagement { private let networkClient: NetworkProtectionClient private let tokenStore: NetworkProtectionTokenStore diff --git a/Sources/NetworkProtection/PacketTunnelProvider.swift b/Sources/NetworkProtection/PacketTunnelProvider.swift index 5795d5221..acf30eb3e 100644 --- a/Sources/NetworkProtection/PacketTunnelProvider.swift +++ b/Sources/NetworkProtection/PacketTunnelProvider.swift @@ -526,24 +526,25 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { private func startTunnel(with tunnelConfiguration: TunnelConfiguration, onDemand: Bool, completionHandler: @escaping (Error?) -> Void) { - adapter.start(tunnelConfiguration: tunnelConfiguration) { error in + adapter.start(tunnelConfiguration: tunnelConfiguration) { [weak self] error in if let error { os_log("🔵 Starting tunnel failed with %{public}@", log: .networkProtection, type: .error, error.localizedDescription) + self?.debugEvents?.fire(error.networkProtectionError) completionHandler(error) return } - Task { - // It's important to call this completiong handler before running the tester + Task { [weak self] in + // It's important to call this completion handler before running the tester // as if we don't, the tester will just fail. It seems like the connection // won't fully work until the completion handler is called. completionHandler(nil) do { let startReason: AdapterStartReason = onDemand ? .onDemand : .manual - try await self.handleAdapterStarted(startReason: startReason) + try await self?.handleAdapterStarted(startReason: startReason) } catch { - self.cancelTunnelWithError(error) + self?.cancelTunnelWithError(error) return } } @@ -556,15 +557,16 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { connectionStatus = .disconnecting os_log("Stopping tunnel with reason %{public}@", log: .networkProtection, type: .info, String(describing: reason)) - adapter.stop { error in + adapter.stop { [weak self] error in if let error { os_log("🔵 Failed to stop WireGuard adapter: %{public}@", log: .networkProtection, type: .info, error.localizedDescription) + self?.debugEvents?.fire(error.networkProtectionError) } - Task { - await self.handleAdapterStopped() + Task { [weak self] in + await self?.handleAdapterStopped() if case .superceded = reason { - self.notificationsPresenter.showSupersededNotification() + self?.notificationsPresenter.showSupersededNotification() } completionHandler() @@ -582,12 +584,13 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { await handleAdapterStopped() } - self.adapter.stop { error in + self.adapter.stop { [weak self] error in if let error = error { os_log("Error while stopping adapter: %{public}@", log: .networkProtection, type: .info, error.localizedDescription) + self?.debugEvents?.fire(error.networkProtectionError) } - self.cancelTunnelWithError(stopError) + self?.cancelTunnelWithError(stopError) } } @@ -656,16 +659,17 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { return } - self.adapter.update(tunnelConfiguration: tunnelConfiguration, reassert: reassert) { error in + self.adapter.update(tunnelConfiguration: tunnelConfiguration, reassert: reassert) { [weak self] error in if let error = error { os_log("🔵 Failed to update the configuration: %{public}@", type: .error, error.localizedDescription) + self?.debugEvents?.fire(error.networkProtectionError) continuation.resume(throwing: error) return } - Task { + Task { [weak self] in do { - try await self.handleAdapterStarted(startReason: .reconnected) + try await self?.handleAdapterStarted(startReason: .reconnected) } catch { continuation.resume(throwing: error) return @@ -850,8 +854,9 @@ open class PacketTunnelProvider: NEPacketTunnelProvider { Task { os_log("Simulating tunnel failure", log: .networkProtection, type: .info) - adapter.stop { error in + adapter.stop { [weak self] error in if let error { + self?.debugEvents?.fire(error.networkProtectionError) os_log("🔵 Failed to stop WireGuard adapter: %{public}@", log: .networkProtection, type: .info, error.localizedDescription) } diff --git a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift index c08795621..ad476c2eb 100644 --- a/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift +++ b/Sources/NetworkProtection/WireGuardKit/WireGuardAdapter.swift @@ -313,7 +313,7 @@ public class WireGuardAdapter { // Tell the system that the tunnel is going to reconnect using new WireGuard // configuration. // This will broadcast the `NEVPNStatusDidChange` notification to the GUI process. - //self.packetTunnelProvider?.reasserting = true + // self.packetTunnelProvider?.reasserting = true } defer { diff --git a/Tests/NetworkProtectionTests/PingerTests.swift b/Tests/NetworkProtectionTests/PingerTests.swift index 60ce487bf..4f19fd1f0 100644 --- a/Tests/NetworkProtectionTests/PingerTests.swift +++ b/Tests/NetworkProtectionTests/PingerTests.swift @@ -23,7 +23,7 @@ import XCTest final class PingerTests: XCTestCase { - func testPingValidIpShouldSucceed() async throws { + func disabled_testPingValidIpShouldSucceed() async throws { let ip = IPv4Address("8.8.8.8")! let timeout = 3.0