Skip to content

Commit

Permalink
Add WireGuard NetP Error Pixels (#508)
Browse files Browse the repository at this point in the history
* Add device manager stuff

* Extract errors to own file

* Add mapping of wireguard errors

* Propogate converted WireguardAdapterErrors

* Fix comment

* Fix bad merge

* Disable pinger test
  • Loading branch information
graeme authored Sep 21, 2023
1 parent e7fc890 commit ec5dc59
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 70 deletions.
80 changes: 80 additions & 0 deletions Sources/NetworkProtection/Diagnostics/NetworkProtectionError.swift
Original file line number Diff line number Diff line change
@@ -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))"
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
53 changes: 0 additions & 53 deletions Sources/NetworkProtection/NetworkProtectionDeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 20 additions & 15 deletions Sources/NetworkProtection/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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()
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Tests/NetworkProtectionTests/PingerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit ec5dc59

Please sign in to comment.