Merge pull request #40 from keeshux/report-tunnel-error-to-host-app
Report tunnel error to host app
This commit is contained in:
commit
d6526301b6
|
@ -74,13 +74,13 @@ extension TunnelKitProvider {
|
|||
public static func deserialized(_ string: String) throws -> EndpointProtocol {
|
||||
let components = string.components(separatedBy: ":")
|
||||
guard components.count == 2 else {
|
||||
throw ProviderError.configuration(field: "endpointProtocol")
|
||||
throw ProviderConfigurationError.parameter(name: "endpointProtocol")
|
||||
}
|
||||
guard let socketType = SocketType(rawValue: components[0]) else {
|
||||
throw ProviderError.configuration(field: "endpointProtocol.socketType")
|
||||
throw ProviderConfigurationError.parameter(name: "endpointProtocol.socketType")
|
||||
}
|
||||
guard let port = UInt16(components[1]) else {
|
||||
throw ProviderError.configuration(field: "endpointProtocol.port")
|
||||
throw ProviderConfigurationError.parameter(name: "endpointProtocol.port")
|
||||
}
|
||||
return EndpointProtocol(socketType, port)
|
||||
}
|
||||
|
@ -163,6 +163,9 @@ extension TunnelKitProvider {
|
|||
/// Optional debug log format (SwiftyBeaver format).
|
||||
public var debugLogFormat: String?
|
||||
|
||||
/// The key in `defaults` where to set the raw value of last `TunnelKitProvider.ProviderError`.
|
||||
public var lastErrorKey: String?
|
||||
|
||||
// MARK: Building
|
||||
|
||||
/**
|
||||
|
@ -188,28 +191,29 @@ extension TunnelKitProvider {
|
|||
shouldDebug = false
|
||||
debugLogKey = nil
|
||||
debugLogFormat = nil
|
||||
lastErrorKey = nil
|
||||
}
|
||||
|
||||
fileprivate init(providerConfiguration: [String: Any]) throws {
|
||||
let S = Configuration.Keys.self
|
||||
|
||||
guard let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String, let cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm) else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.cipherAlgorithm)]")
|
||||
}
|
||||
guard let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String, let digest = SessionProxy.Digest(rawValue: digestAlgorithm) else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]")
|
||||
}
|
||||
|
||||
let ca: CryptoContainer
|
||||
let clientCertificate: CryptoContainer?
|
||||
let clientKey: CryptoContainer?
|
||||
guard let caPEM = providerConfiguration[S.ca] as? String else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.ca)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.ca)]")
|
||||
}
|
||||
ca = CryptoContainer(pem: caPEM)
|
||||
if let clientPEM = providerConfiguration[S.clientCertificate] as? String {
|
||||
guard let keyPEM = providerConfiguration[S.clientKey] as? String else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
|
||||
}
|
||||
|
||||
clientCertificate = CryptoContainer(pem: clientPEM)
|
||||
|
@ -223,7 +227,7 @@ extension TunnelKitProvider {
|
|||
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
|
||||
|
||||
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
|
||||
}
|
||||
endpointProtocols = try endpointProtocolsStrings.map { try EndpointProtocol.deserialized($0) }
|
||||
|
||||
|
@ -242,7 +246,7 @@ extension TunnelKitProvider {
|
|||
do {
|
||||
tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData)
|
||||
} catch {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
|
||||
}
|
||||
}
|
||||
keepAliveSeconds = providerConfiguration[S.keepAlive] as? Int
|
||||
|
@ -252,16 +256,17 @@ extension TunnelKitProvider {
|
|||
shouldDebug = providerConfiguration[S.debug] as? Bool ?? false
|
||||
if shouldDebug {
|
||||
guard let debugLogKey = providerConfiguration[S.debugLogKey] as? String else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.debugLogKey)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.debugLogKey)]")
|
||||
}
|
||||
self.debugLogKey = debugLogKey
|
||||
debugLogFormat = providerConfiguration[S.debugLogFormat] as? String
|
||||
} else {
|
||||
debugLogKey = nil
|
||||
}
|
||||
lastErrorKey = providerConfiguration[S.lastErrorKey] as? String
|
||||
|
||||
guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,7 +293,8 @@ extension TunnelKitProvider {
|
|||
usesPIAPatches: usesPIAPatches,
|
||||
shouldDebug: shouldDebug,
|
||||
debugLogKey: shouldDebug ? debugLogKey : nil,
|
||||
debugLogFormat: shouldDebug ? debugLogFormat : nil
|
||||
debugLogFormat: shouldDebug ? debugLogFormat : nil,
|
||||
lastErrorKey: lastErrorKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -331,6 +337,8 @@ extension TunnelKitProvider {
|
|||
static let debugLogKey = "DebugLogKey"
|
||||
|
||||
static let debugLogFormat = "DebugLogFormat"
|
||||
|
||||
static let lastErrorKey = "LastErrorKey"
|
||||
}
|
||||
|
||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.prefersResolvedAddresses`
|
||||
|
@ -384,6 +392,9 @@ extension TunnelKitProvider {
|
|||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.debugLogFormat`
|
||||
public let debugLogFormat: String?
|
||||
|
||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.lastErrorKey`
|
||||
public let lastErrorKey: String?
|
||||
|
||||
// MARK: Shortcuts
|
||||
|
||||
func existingLog(in defaults: UserDefaults) -> [String]? {
|
||||
|
@ -404,7 +415,7 @@ extension TunnelKitProvider {
|
|||
*/
|
||||
public static func appGroup(from providerConfiguration: [String: Any]) throws -> String {
|
||||
guard let appGroup = providerConfiguration[Keys.appGroup] as? String else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
|
||||
}
|
||||
return appGroup
|
||||
}
|
||||
|
@ -468,6 +479,9 @@ extension TunnelKitProvider {
|
|||
if let debugLogFormat = debugLogFormat {
|
||||
dict[S.debugLogFormat] = debugLogFormat
|
||||
}
|
||||
if let lastErrorKey = lastErrorKey {
|
||||
dict[S.lastErrorKey] = lastErrorKey
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
|
@ -491,7 +505,7 @@ extension TunnelKitProvider {
|
|||
do {
|
||||
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)
|
||||
} catch _ {
|
||||
throw ProviderError.credentials(field: "keychain.set()")
|
||||
throw ProviderConfigurationError.credentials(details: "keychain.set()")
|
||||
}
|
||||
protocolConfiguration.username = username
|
||||
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username)
|
||||
|
@ -562,6 +576,7 @@ extension TunnelKitProvider.Configuration: Equatable {
|
|||
builder.shouldDebug = shouldDebug
|
||||
builder.debugLogKey = debugLogKey
|
||||
builder.debugLogFormat = debugLogFormat
|
||||
builder.lastErrorKey = lastErrorKey
|
||||
return builder
|
||||
}
|
||||
|
||||
|
|
|
@ -71,20 +71,24 @@ extension TunnelKitProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/// The errors raised by `TunnelKitProvider`.
|
||||
public enum ProviderError: Error {
|
||||
|
||||
/// The `TunnelKitProvider.Configuration` provided is incorrect or incomplete.
|
||||
case configuration(field: String)
|
||||
// mostly programming errors by host app
|
||||
enum ProviderConfigurationError: Error {
|
||||
|
||||
/// Credentials are missing or protected (e.g. device locked).
|
||||
case credentials(field: String)
|
||||
/// A field in the `TunnelKitProvider.Configuration` provided is incorrect or incomplete.
|
||||
case parameter(name: String)
|
||||
|
||||
/// Credentials are missing or inaccessible.
|
||||
case credentials(details: String)
|
||||
|
||||
/// The pseudo-random number generator could not be initialized.
|
||||
case prngInitialization
|
||||
|
||||
/// The TLS certificate could not be serialized.
|
||||
case certificateSerialization
|
||||
}
|
||||
|
||||
/// The errors causing a tunnel disconnection.
|
||||
public enum ProviderError: String, Error {
|
||||
|
||||
/// Socket endpoint could not be resolved.
|
||||
case dnsFailure
|
||||
|
@ -95,10 +99,22 @@ extension TunnelKitProvider {
|
|||
/// Socket failed to reach active state.
|
||||
case socketActivity
|
||||
|
||||
/// Credentials authentication failed.
|
||||
case authenticationFailed
|
||||
|
||||
/// TLS handshake failed.
|
||||
case tlsFailed
|
||||
|
||||
/// Tunnel timed out.
|
||||
case timeout
|
||||
|
||||
/// An error occurred at the link level.
|
||||
case linkError
|
||||
|
||||
/// The current network changed (e.g. switched from WiFi to data connection).
|
||||
case networkChanged
|
||||
|
||||
/// The server replied in an unexpected way.
|
||||
case unexpectedReply
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,23 +121,23 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
let hostname: String
|
||||
do {
|
||||
guard let tunnelProtocol = protocolConfiguration as? NETunnelProviderProtocol else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration")
|
||||
}
|
||||
guard let serverAddress = tunnelProtocol.serverAddress else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.serverAddress")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.serverAddress")
|
||||
}
|
||||
guard let providerConfiguration = tunnelProtocol.providerConfiguration else {
|
||||
throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration")
|
||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration")
|
||||
}
|
||||
hostname = serverAddress
|
||||
try appGroup = Configuration.appGroup(from: providerConfiguration)
|
||||
try cfg = Configuration.parsed(from: providerConfiguration)
|
||||
} catch let e {
|
||||
var message: String?
|
||||
if let te = e as? ProviderError {
|
||||
if let te = e as? ProviderConfigurationError {
|
||||
switch te {
|
||||
case .configuration(let field):
|
||||
message = "Tunnel configuration incomplete: \(field)"
|
||||
case .parameter(let name):
|
||||
message = "Tunnel configuration incomplete: \(name)"
|
||||
|
||||
default:
|
||||
break
|
||||
|
@ -176,9 +176,10 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
)
|
||||
|
||||
log.info("Starting tunnel...")
|
||||
clearErrorStatus()
|
||||
|
||||
guard SessionProxy.EncryptionBridge.prepareRandomNumberGenerator(seedLength: prngSeedLength) else {
|
||||
completionHandler(ProviderError.prngInitialization)
|
||||
completionHandler(ProviderConfigurationError.prngInitialization)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -190,7 +191,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
try cfg.ca.write(to: url)
|
||||
caPath = url.path
|
||||
} catch {
|
||||
completionHandler(ProviderError.certificateSerialization)
|
||||
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||
return
|
||||
}
|
||||
if let clientCertificate = cfg.clientCertificate {
|
||||
|
@ -199,7 +200,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
try clientCertificate.write(to: url)
|
||||
clientCertificatePath = url.path
|
||||
} catch {
|
||||
completionHandler(ProviderError.certificateSerialization)
|
||||
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -211,7 +212,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
try clientKey.write(to: url)
|
||||
clientKeyPath = url.path
|
||||
} catch {
|
||||
completionHandler(ProviderError.certificateSerialization)
|
||||
completionHandler(ProviderConfigurationError.certificateSerialization)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -259,6 +260,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
open override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
pendingStartHandler = nil
|
||||
log.info("Stopping tunnel...")
|
||||
clearErrorStatus()
|
||||
|
||||
guard let proxy = proxy else {
|
||||
flushLog()
|
||||
|
@ -324,6 +326,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
|
||||
private func connectTunnel(via socket: GenericSocket) {
|
||||
log.info("Will connect to \(socket)")
|
||||
clearErrorStatus()
|
||||
|
||||
log.debug("Socket type is \(type(of: socket))")
|
||||
self.socket = socket
|
||||
|
@ -342,6 +345,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
|
||||
if let error = error {
|
||||
log.error("Tunnel did stop (error: \(error))")
|
||||
setErrorStatus(with: error)
|
||||
} else {
|
||||
log.info("Tunnel did stop on request")
|
||||
}
|
||||
|
@ -349,7 +353,7 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
|||
|
||||
private func disposeTunnel(error: Error?) {
|
||||
flushLog()
|
||||
|
||||
|
||||
// failed to start
|
||||
if (pendingStartHandler != nil) {
|
||||
|
||||
|
@ -599,6 +603,38 @@ extension TunnelKitProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private func setErrorStatus(with error: Error) {
|
||||
guard let lastErrorKey = cfg.lastErrorKey else {
|
||||
return
|
||||
}
|
||||
let providerError: ProviderError
|
||||
if let se = error as? SessionError {
|
||||
switch se {
|
||||
case .badCredentials:
|
||||
providerError = .authenticationFailed
|
||||
|
||||
case .peerVerification, .tlsError:
|
||||
providerError = .tlsFailed
|
||||
|
||||
case .negotiationTimeout, .pingTimeout:
|
||||
providerError = .timeout
|
||||
|
||||
default:
|
||||
providerError = .unexpectedReply
|
||||
}
|
||||
} else {
|
||||
providerError = error as? ProviderError ?? .linkError
|
||||
}
|
||||
defaults?.set(providerError.rawValue, forKey: lastErrorKey)
|
||||
}
|
||||
|
||||
private func clearErrorStatus() {
|
||||
guard let lastErrorKey = cfg.lastErrorKey else {
|
||||
return
|
||||
}
|
||||
defaults?.removeObject(forKey: lastErrorKey)
|
||||
}
|
||||
|
||||
private func logCurrentSSID() {
|
||||
if let ssid = observer.currentWifiNetworkName() {
|
||||
log.debug("Current SSID: '\(ssid)'")
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
import Foundation
|
||||
|
||||
/// The possible errors raised/thrown during `SessionProxy` operation.
|
||||
public enum SessionError: Error {
|
||||
public enum SessionError: String, Error {
|
||||
|
||||
/// The negotiation timed out.
|
||||
case negotiationTimeout
|
||||
|
|
Loading…
Reference in New Issue