summaryrefslogtreecommitdiffhomepage
path: root/ios/PacketTunnelCore
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-09-20 15:15:25 +0200
committerBug Magnet <marco.nikic@mullvad.net>2023-09-21 13:42:23 +0200
commite98d4400c9287c377ebb46301e34c4bdd165cc43 (patch)
tree34756cf9a55ebf4e78705c3bc0b6e666f9ecfe9c /ios/PacketTunnelCore
parentdb1ffbbfb157b419c303f8d87182f2acc3da9a0a (diff)
downloadmullvadvpn-e98d4400c9287c377ebb46301e34c4bdd165cc43.tar.xz
mullvadvpn-e98d4400c9287c377ebb46301e34c4bdd165cc43.zip
Merge TunnelProviderMessaging into PacketTunnelCore
Diffstat (limited to 'ios/PacketTunnelCore')
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelErrorWrapper.swift49
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift63
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift37
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift112
-rw-r--r--ios/PacketTunnelCore/IPC/RelaySelectorResult+PacketTunnelRelay.swift21
-rw-r--r--ios/PacketTunnelCore/IPC/TunnelProviderMessage.swift52
-rw-r--r--ios/PacketTunnelCore/IPC/TunnelProviderReply.swift26
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift36
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift65
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift75
10 files changed, 536 insertions, 0 deletions
diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelErrorWrapper.swift b/ios/PacketTunnelCore/IPC/PacketTunnelErrorWrapper.swift
new file mode 100644
index 0000000000..381b64898a
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/PacketTunnelErrorWrapper.swift
@@ -0,0 +1,49 @@
+//
+// PacketTunnelErrorWrapper.swift
+// MullvadTypes
+//
+// Created by Sajad Vishkai on 2022-11-28.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public enum PacketTunnelErrorWrapper: Codable, Equatable, LocalizedError {
+ public enum ConfigurationFailureCause: Codable, Equatable {
+ /// Device is locked.
+ case deviceLocked
+
+ /// Settings schema is outdated.
+ case outdatedSchema
+
+ /// No relay satisfying constraints.
+ case noRelaysSatisfyingConstraints
+
+ /// Read error.
+ case readFailure
+ }
+
+ /// Failure that indicates WireGuard errors.
+ case wireguard(String)
+
+ /// Failure to read stored settings.
+ case configuration(ConfigurationFailureCause)
+
+ public var errorDescription: String? {
+ switch self {
+ case let .wireguard(error):
+ return error
+ case let .configuration(cause):
+ switch cause {
+ case .deviceLocked:
+ return "Device is locked."
+ case .outdatedSchema:
+ return "Settings schema is outdated."
+ case .readFailure:
+ return "Failure to read VPN configuration."
+ case .noRelaysSatisfyingConstraints:
+ return "No relays satisfying constraints."
+ }
+ }
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift b/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift
new file mode 100644
index 0000000000..8290895294
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift
@@ -0,0 +1,63 @@
+//
+// PacketTunnelOptions.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 22/08/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import RelaySelector
+
+public struct PacketTunnelOptions {
+ /// Keys for options dictionary
+ private enum Keys: String {
+ /// Option key that holds the `NSData` value with `RelaySelectorResult`
+ /// encoded using `JSONEncoder`.
+ /// Used for passing the pre-selected relay in the GUI process to the Packet tunnel process.
+ case relaySelectorResult = "relay-selector-result"
+
+ /// Option key that holds the `NSNumber` value, which is when set to `1` indicates that
+ /// the tunnel was started by the system.
+ /// System automatically provides that flag to the tunnel.
+ case isOnDemand = "is-on-demand"
+ }
+
+ private var _rawOptions: [String: NSObject]
+
+ public func rawOptions() -> [String: NSObject] {
+ _rawOptions
+ }
+
+ public init() {
+ _rawOptions = [:]
+ }
+
+ public init(rawOptions: [String: NSObject]) {
+ _rawOptions = rawOptions
+ }
+
+ public func getSelectorResult() throws -> RelaySelectorResult? {
+ guard let data = _rawOptions[Keys.relaySelectorResult.rawValue] as? Data else { return nil }
+
+ return try Self.decode(RelaySelectorResult.self, data)
+ }
+
+ public mutating func setSelectorResult(_ value: RelaySelectorResult) throws {
+ _rawOptions[Keys.relaySelectorResult.rawValue] = try Self.encode(value) as NSData
+ }
+
+ public func isOnDemand() -> Bool {
+ _rawOptions[Keys.isOnDemand.rawValue] as? Int == 1
+ }
+
+ /// Encode custom parameter value
+ private static func encode(_ value: some Codable) throws -> Data {
+ try JSONEncoder().encode(value)
+ }
+
+ /// Decode custom parameter value
+ private static func decode<T: Codable>(_ type: T.Type, _ data: Data) throws -> T {
+ try JSONDecoder().decode(T.self, from: data)
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift b/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift
new file mode 100644
index 0000000000..4f69837240
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift
@@ -0,0 +1,37 @@
+//
+// PacketTunnelRelay.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 21/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+
+/// Struct holding tunnel relay information.
+public struct PacketTunnelRelay: Codable, Equatable {
+ /// IPv4 relay endpoint.
+ public let ipv4Relay: IPv4Endpoint
+
+ /// IPv6 relay endpoint.
+ public let ipv6Relay: IPv6Endpoint?
+
+ /// Relay hostname.
+ public let hostname: String
+
+ /// Relay location.
+ public let location: Location
+
+ public init(
+ ipv4Relay: IPv4Endpoint,
+ ipv6Relay: IPv6Endpoint? = nil,
+ hostname: String,
+ location: Location
+ ) {
+ self.ipv4Relay = ipv4Relay
+ self.ipv6Relay = ipv6Relay
+ self.hostname = hostname
+ self.location = location
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift b/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift
new file mode 100644
index 0000000000..5eece926a6
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift
@@ -0,0 +1,112 @@
+//
+// PacketTunnelStatus.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 27/07/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadTypes
+
+/// The verdict of an account status check.
+public enum AccountVerdict: Equatable, Codable {
+ /// Account is no longer valid.
+ case invalid
+
+ /// Account is expired.
+ case expired(Account)
+
+ /// Account exists and has enough time left.
+ case active(Account)
+}
+
+/// The verdict of a device status check.
+public enum DeviceVerdict: Equatable, Codable {
+ /// Device is revoked.
+ case revoked
+
+ /// Device exists but the public key registered on server does not match any longer.
+ case keyMismatch
+
+ /// Device is in good standing and should work as normal.
+ case active
+}
+
+/// Type describing whether key rotation took place and the outcome of it.
+public enum KeyRotationStatus: Equatable, Codable {
+ /// No rotation took place yet.
+ case noAction
+
+ /// Rotation attempt took place but without success.
+ case attempted(Date)
+
+ /// Rotation attempt took place and succeeded.
+ case succeeded(Date)
+
+ /// Returns `true` if the status is `.succeeded`.
+ public var isSucceeded: Bool {
+ if case .succeeded = self {
+ return true
+ } else {
+ return false
+ }
+ }
+}
+
+/**
+ Struct holding data associated with account and device diagnostics and also device key recovery performed by packet
+ tunnel process.
+ */
+public struct DeviceCheck: Codable, Equatable {
+ /// The verdict of account status check.
+ public var accountVerdict: AccountVerdict
+
+ /// The verdict of device status check.
+ public var deviceVerdict: DeviceVerdict
+
+ // The status of the last performed key rotation.
+ public var keyRotationStatus: KeyRotationStatus
+
+ public init(
+ accountVerdict: AccountVerdict,
+ deviceVerdict: DeviceVerdict,
+ keyRotationStatus: KeyRotationStatus
+ ) {
+ self.accountVerdict = accountVerdict
+ self.deviceVerdict = deviceVerdict
+ self.keyRotationStatus = keyRotationStatus
+ }
+}
+
+/// Struct describing packet tunnel process status.
+public struct PacketTunnelStatus: Codable, Equatable {
+ /// Last tunnel error.
+ public var lastErrors: [PacketTunnelErrorWrapper]
+
+ /// Flag indicating whether network is reachable.
+ public var isNetworkReachable: Bool
+
+ /// Last performed device check.
+ public var deviceCheck: DeviceCheck?
+
+ /// Current relay.
+ public var tunnelRelay: PacketTunnelRelay?
+
+ /// Number of consecutive connection failure attempts.
+ public var numberOfFailedAttempts: UInt
+
+ public init(
+ lastErrors: [PacketTunnelErrorWrapper] = [],
+ isNetworkReachable: Bool = true,
+ deviceCheck: DeviceCheck? = nil,
+ tunnelRelay: PacketTunnelRelay? = nil,
+ numberOfFailedAttempts: UInt = 0
+ ) {
+ self.lastErrors = lastErrors
+ self.isNetworkReachable = isNetworkReachable
+ self.deviceCheck = deviceCheck
+ self.tunnelRelay = tunnelRelay
+ self.numberOfFailedAttempts = numberOfFailedAttempts
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/RelaySelectorResult+PacketTunnelRelay.swift b/ios/PacketTunnelCore/IPC/RelaySelectorResult+PacketTunnelRelay.swift
new file mode 100644
index 0000000000..4169f7bca8
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/RelaySelectorResult+PacketTunnelRelay.swift
@@ -0,0 +1,21 @@
+//
+// RelaySelectorResult+PacketTunnelRelay.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 20/09/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import RelaySelector
+
+extension RelaySelectorResult {
+ public var packetTunnelRelay: PacketTunnelRelay {
+ PacketTunnelRelay(
+ ipv4Relay: endpoint.ipv4Relay,
+ ipv6Relay: endpoint.ipv6Relay,
+ hostname: relay.hostname,
+ location: location
+ )
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/TunnelProviderMessage.swift b/ios/PacketTunnelCore/IPC/TunnelProviderMessage.swift
new file mode 100644
index 0000000000..231c1cd858
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/TunnelProviderMessage.swift
@@ -0,0 +1,52 @@
+//
+// TunnelProviderMessage.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 27/07/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import RelaySelector
+
+/// Enum describing supported app messages handled by packet tunnel provider.
+public enum TunnelProviderMessage: Codable, CustomStringConvertible {
+ /// Request the tunnel to reconnect.
+ /// The packet tunnel reconnects to the current relay when selector result is `nil`.
+ case reconnectTunnel(RelaySelectorResult?)
+
+ /// Request the tunnel status.
+ case getTunnelStatus
+
+ /// Send HTTP request outside of VPN tunnel.
+ case sendURLRequest(ProxyURLRequest)
+
+ /// Cancel HTTP request sent outside of VPN tunnel.
+ case cancelURLRequest(UUID)
+
+ /// Notify tunnel about private key rotation.
+ case privateKeyRotation
+
+ public var description: String {
+ switch self {
+ case .reconnectTunnel:
+ return "reconnect-tunnel"
+ case .getTunnelStatus:
+ return "get-tunnel-status"
+ case .sendURLRequest:
+ return "send-http-request"
+ case .cancelURLRequest:
+ return "cancel-http-request"
+ case .privateKeyRotation:
+ return "private-key-rotation"
+ }
+ }
+
+ public init(messageData: Data) throws {
+ self = try JSONDecoder().decode(Self.self, from: messageData)
+ }
+
+ public func encode() throws -> Data {
+ try JSONEncoder().encode(self)
+ }
+}
diff --git a/ios/PacketTunnelCore/IPC/TunnelProviderReply.swift b/ios/PacketTunnelCore/IPC/TunnelProviderReply.swift
new file mode 100644
index 0000000000..d0c0c80b1d
--- /dev/null
+++ b/ios/PacketTunnelCore/IPC/TunnelProviderReply.swift
@@ -0,0 +1,26 @@
+//
+// TunnelProviderReply.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 20/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Container type for tunnel provider reply.
+public struct TunnelProviderReply<T: Codable>: Codable {
+ public var value: T
+
+ public init(_ value: T) {
+ self.value = value
+ }
+
+ public init(messageData: Data) throws {
+ self = try JSONDecoder().decode(Self.self, from: messageData)
+ }
+
+ public func encode() throws -> Data {
+ try JSONEncoder().encode(self)
+ }
+}
diff --git a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift
new file mode 100644
index 0000000000..e7b52c36c2
--- /dev/null
+++ b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift
@@ -0,0 +1,36 @@
+//
+// ProxyURLRequest.swift
+// PacketTunnelCore
+//
+// Created by Sajad Vishkai on 2022-10-03.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Struct describing serializable URLRequest data.
+public struct ProxyURLRequest: Codable {
+ public let id: UUID
+ public let url: URL
+ public let method: String?
+ public let httpBody: Data?
+ public let httpHeaders: [String: String]?
+
+ public var urlRequest: URLRequest {
+ var urlRequest = URLRequest(url: url)
+ urlRequest.httpMethod = method
+ urlRequest.httpBody = httpBody
+ urlRequest.allHTTPHeaderFields = httpHeaders
+ return urlRequest
+ }
+
+ public init?(id: UUID, urlRequest: URLRequest) {
+ guard let urlRequestUrl = urlRequest.url else { return nil }
+
+ self.id = id
+ url = urlRequestUrl
+ method = urlRequest.httpMethod
+ httpBody = urlRequest.httpBody
+ httpHeaders = urlRequest.allHTTPHeaderFields
+ }
+}
diff --git a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift
new file mode 100644
index 0000000000..1e13bf9b83
--- /dev/null
+++ b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLResponse.swift
@@ -0,0 +1,65 @@
+//
+// ProxyURLResponse.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 20/10/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Struct describing serializable URLResponse data.
+public struct ProxyURLResponse: Codable {
+ public let data: Data?
+ public let response: HTTPURLResponseWrapper?
+ public let error: URLErrorWrapper?
+
+ public init(data: Data?, response: URLResponse?, error: Error?) {
+ self.data = data
+ self.response = response.flatMap { HTTPURLResponseWrapper($0) }
+ self.error = error.flatMap { URLErrorWrapper($0) }
+ }
+}
+
+public struct URLErrorWrapper: Codable {
+ public let code: Int?
+ public let localizedDescription: String
+
+ public init?(_ error: Error) {
+ localizedDescription = error.localizedDescription
+ code = (error as? URLError)?.errorCode
+ }
+
+ public var originalError: Error? {
+ guard let code else { return nil }
+
+ return URLError(URLError.Code(rawValue: code))
+ }
+}
+
+public struct HTTPURLResponseWrapper: Codable {
+ public let url: URL?
+ public let statusCode: Int
+ public let headerFields: [String: String]?
+
+ public init?(_ response: URLResponse) {
+ guard let response = response as? HTTPURLResponse else { return nil }
+
+ url = response.url
+ statusCode = response.statusCode
+ headerFields = Dictionary(
+ uniqueKeysWithValues: response.allHeaderFields.map { ("\($0)", "\($1)") }
+ )
+ }
+
+ public var originalResponse: HTTPURLResponse? {
+ guard let url else { return nil }
+
+ return HTTPURLResponse(
+ url: url,
+ statusCode: statusCode,
+ httpVersion: nil,
+ headerFields: headerFields
+ )
+ }
+}
diff --git a/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift
new file mode 100644
index 0000000000..a5b3be3bc2
--- /dev/null
+++ b/ios/PacketTunnelCore/URLRequestProxy/URLRequestProxy.swift
@@ -0,0 +1,75 @@
+//
+// URLRequestProxy.swift
+// PacketTunnelCore
+//
+// Created by pronebird on 03/02/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadREST
+import MullvadTransport
+import MullvadTypes
+
+public final class URLRequestProxy {
+ /// Serial queue used for synchronizing access to class members.
+ private let dispatchQueue: DispatchQueue
+
+ private let transportProvider: RESTTransportProvider
+
+ /// List of all proxied network requests bypassing VPN.
+ private var proxiedRequests: [UUID: Cancellable] = [:]
+
+ public init(
+ dispatchQueue: DispatchQueue,
+ transportProvider: RESTTransportProvider
+ ) {
+ self.dispatchQueue = dispatchQueue
+ self.transportProvider = transportProvider
+ }
+
+ public func sendRequest(
+ _ proxyRequest: ProxyURLRequest,
+ completionHandler: @escaping (ProxyURLResponse) -> Void
+ ) {
+ dispatchQueue.async {
+ guard let transportProvider = self.transportProvider.makeTransport() else { return }
+
+ // The task sent by `transport.sendRequest` comes in an already resumed state
+ let task = transportProvider.sendRequest(proxyRequest.urlRequest) { [weak self] data, response, error in
+ guard let self else { return }
+ // However there is no guarantee about which queue the execution resumes on
+ // Use `dispatchQueue` to guarantee thread safe access to `proxiedRequests`
+ dispatchQueue.async {
+ let response = ProxyURLResponse(data: data, response: response, error: error)
+ _ = self.removeRequest(identifier: proxyRequest.id)
+
+ completionHandler(response)
+ }
+ }
+
+ // All tasks should have unique identifiers, but if not, cancel the task scheduled
+ // earlier.
+ let oldTask = self.addRequest(identifier: proxyRequest.id, task: task)
+ oldTask?.cancel()
+ }
+ }
+
+ public func cancelRequest(identifier: UUID) {
+ dispatchQueue.async {
+ let task = self.removeRequest(identifier: identifier)
+
+ task?.cancel()
+ }
+ }
+
+ private func addRequest(identifier: UUID, task: Cancellable) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
+ return proxiedRequests.updateValue(task, forKey: identifier)
+ }
+
+ private func removeRequest(identifier: UUID) -> Cancellable? {
+ dispatchPrecondition(condition: .onQueue(dispatchQueue))
+ return proxiedRequests.removeValue(forKey: identifier)
+ }
+}