Merge pull request #40 from keeshux/report-tunnel-error-to-host-app

Report tunnel error to host app
This commit is contained in:
Davide De Rosa 2018-10-22 15:47:22 +02:00 committed by GitHub
commit d6526301b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 33 deletions

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)'")

View File

@ -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