tunnelkit/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configura...

581 lines
26 KiB
Swift
Raw Normal View History

2018-08-23 08:19:25 +00:00
//
// TunnelKitProvider+Configuration.swift
// TunnelKit
2018-08-23 08:19:25 +00:00
//
// Created by Davide De Rosa on 10/23/17.
2019-03-09 10:44:18 +00:00
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/keeshux
//
// This file is part of TunnelKit.
//
// TunnelKit is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// TunnelKit is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (c) 2018-Present Private Internet Access
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
2018-08-23 08:19:25 +00:00
//
import Foundation
import NetworkExtension
import SwiftyBeaver
private let log = SwiftyBeaver.self
extension TunnelKitProvider {
2018-08-23 08:19:25 +00:00
// MARK: Configuration
/// The way to create a `TunnelKitProvider.Configuration` object for the tunnel profile.
2018-08-23 08:19:25 +00:00
public struct ConfigurationBuilder {
/// :nodoc:
public static let defaults = Configuration(
prefersResolvedAddresses: false,
resolvedAddresses: nil,
endpointProtocols: nil,
mtu: 1250,
sessionConfiguration: SessionProxy.ConfigurationBuilder().build(),
shouldDebug: false,
debugLogFormat: nil,
masksPrivateData: true
)
2018-08-23 08:19:25 +00:00
/// Prefers resolved addresses over DNS resolution. `resolvedAddresses` must be set and non-empty. Default is `false`.
///
/// - Seealso: `fallbackServerAddresses`
public var prefersResolvedAddresses: Bool
/// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true`.
public var resolvedAddresses: [String]?
/// The MTU of the link.
public var mtu: Int
2018-08-23 08:19:25 +00:00
/// The session configuration.
public var sessionConfiguration: SessionProxy.Configuration
2018-08-23 08:19:25 +00:00
// MARK: Debugging
/// Enables debugging.
2018-08-23 08:19:25 +00:00
public var shouldDebug: Bool
/// Optional debug log format (SwiftyBeaver format).
public var debugLogFormat: String?
/// Mask private data in debug log (default is `true`).
public var masksPrivateData: Bool?
2018-08-23 08:19:25 +00:00
// MARK: Building
/**
Default initializer.
- Parameter ca: The CA certificate.
2018-08-23 08:19:25 +00:00
*/
public init(sessionConfiguration: SessionProxy.Configuration) {
prefersResolvedAddresses = ConfigurationBuilder.defaults.prefersResolvedAddresses
2018-08-23 08:19:25 +00:00
resolvedAddresses = nil
mtu = ConfigurationBuilder.defaults.mtu
self.sessionConfiguration = sessionConfiguration
shouldDebug = ConfigurationBuilder.defaults.shouldDebug
debugLogFormat = ConfigurationBuilder.defaults.debugLogFormat
masksPrivateData = ConfigurationBuilder.defaults.masksPrivateData
2018-08-23 08:19:25 +00:00
}
fileprivate init(providerConfiguration: [String: Any]) throws {
let S = Configuration.Keys.self
prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? ConfigurationBuilder.defaults.prefersResolvedAddresses
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
mtu = providerConfiguration[S.mtu] as? Int ?? ConfigurationBuilder.defaults.mtu
//
guard let caPEM = providerConfiguration[S.ca] as? String else {
2018-10-21 10:58:36 +00:00
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.ca)]")
2018-08-23 08:19:25 +00:00
}
var sessionConfigurationBuilder = SessionProxy.ConfigurationBuilder()
if let cipherAlgorithm = providerConfiguration[S.cipherAlgorithm] as? String {
sessionConfigurationBuilder.cipher = SessionProxy.Cipher(rawValue: cipherAlgorithm)
}
if let digestAlgorithm = providerConfiguration[S.digestAlgorithm] as? String {
sessionConfigurationBuilder.digest = SessionProxy.Digest(rawValue: digestAlgorithm)
}
if let compressionFramingValue = providerConfiguration[S.compressionFraming] as? Int, let compressionFraming = SessionProxy.CompressionFraming(rawValue: compressionFramingValue) {
sessionConfigurationBuilder.compressionFraming = compressionFraming
} else {
sessionConfigurationBuilder.compressionFraming = ConfigurationBuilder.defaults.sessionConfiguration.compressionFraming
}
if let compressionAlgorithmValue = providerConfiguration[S.compressionAlgorithm] as? Int, let compressionAlgorithm = SessionProxy.CompressionAlgorithm(rawValue: compressionAlgorithmValue) {
sessionConfigurationBuilder.compressionAlgorithm = compressionAlgorithm
} else {
sessionConfigurationBuilder.compressionAlgorithm = ConfigurationBuilder.defaults.sessionConfiguration.compressionAlgorithm
}
sessionConfigurationBuilder.ca = CryptoContainer(pem: caPEM)
if let clientPEM = providerConfiguration[S.clientCertificate] as? String {
guard let keyPEM = providerConfiguration[S.clientKey] as? String else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.clientKey)]")
}
sessionConfigurationBuilder.clientCertificate = CryptoContainer(pem: clientPEM)
sessionConfigurationBuilder.clientKey = CryptoContainer(pem: keyPEM)
}
if let tlsWrapData = providerConfiguration[S.tlsWrap] as? Data {
do {
sessionConfigurationBuilder.tlsWrap = try SessionProxy.TLSWrap.deserialized(tlsWrapData)
} catch {
2018-10-21 10:58:36 +00:00
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]")
}
}
sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.keepAliveInterval
sessionConfigurationBuilder.renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.renegotiatesAfter
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
}
sessionConfigurationBuilder.endpointProtocols = try endpointProtocolsStrings.map {
guard let ep = EndpointProtocol(rawValue: $0) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] has a badly formed element")
}
return ep
}
sessionConfigurationBuilder.checksEKU = providerConfiguration[S.checksEKU] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.checksEKU
sessionConfigurationBuilder.randomizeEndpoint = providerConfiguration[S.randomizeEndpoint] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.randomizeEndpoint
sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.usesPIAPatches
sessionConfigurationBuilder.dnsServers = providerConfiguration[S.dnsServers] as? [String]
sessionConfigurationBuilder.searchDomain = providerConfiguration[S.searchDomain] as? String
sessionConfiguration = sessionConfigurationBuilder.build()
2018-08-23 08:19:25 +00:00
shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug
2018-08-23 08:19:25 +00:00
if shouldDebug {
debugLogFormat = providerConfiguration[S.debugLogFormat] as? String
}
masksPrivateData = providerConfiguration[S.masksPrivateData] as? Bool ?? ConfigurationBuilder.defaults.masksPrivateData
2018-08-23 08:19:25 +00:00
guard !prefersResolvedAddresses || !(resolvedAddresses?.isEmpty ?? true) else {
2018-10-21 10:58:36 +00:00
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.prefersResolvedAddresses)] is true but no [\(S.resolvedAddresses)]")
2018-08-23 08:19:25 +00:00
}
}
/**
Builds a `TunnelKitProvider.Configuration` object that will connect to the provided endpoint.
2018-08-23 08:19:25 +00:00
- Returns: A `TunnelKitProvider.Configuration` object with this builder and the additional method parameters.
2018-08-23 08:19:25 +00:00
*/
public func build() -> Configuration {
return Configuration(
prefersResolvedAddresses: prefersResolvedAddresses,
resolvedAddresses: resolvedAddresses,
endpointProtocols: nil,
2018-08-23 08:19:25 +00:00
mtu: mtu,
sessionConfiguration: sessionConfiguration,
2018-08-23 08:19:25 +00:00
shouldDebug: shouldDebug,
debugLogFormat: shouldDebug ? debugLogFormat : nil,
masksPrivateData: masksPrivateData
2018-08-23 08:19:25 +00:00
)
}
}
/// Offers a bridge between the abstract `TunnelKitProvider.ConfigurationBuilder` and a concrete `NETunnelProviderProtocol` profile.
public struct Configuration: Codable {
2018-08-23 08:19:25 +00:00
struct Keys {
static let appGroup = "AppGroup"
static let prefersResolvedAddresses = "PrefersResolvedAddresses"
static let resolvedAddresses = "ResolvedAddresses"
static let mtu = "MTU"
// MARK: SessionConfiguration
2018-08-23 08:19:25 +00:00
static let cipherAlgorithm = "CipherAlgorithm"
static let digestAlgorithm = "DigestAlgorithm"
static let compressionFraming = "CompressionFraming"
static let compressionAlgorithm = "CompressionAlgorithm"
2018-08-23 08:19:25 +00:00
static let ca = "CA"
static let clientCertificate = "ClientCertificate"
static let clientKey = "ClientKey"
static let tlsWrap = "TLSWrap"
static let keepAlive = "KeepAlive"
static let endpointProtocols = "EndpointProtocols"
2018-08-23 08:19:25 +00:00
static let renegotiatesAfter = "RenegotiatesAfter"
static let checksEKU = "ChecksEKU"
static let randomizeEndpoint = "RandomizeEndpoint"
static let usesPIAPatches = "UsesPIAPatches"
static let dnsServers = "DNSServers"
static let searchDomain = "SearchDomain"
// MARK: Debugging
2018-08-23 08:19:25 +00:00
static let debug = "Debug"
static let debugLogFormat = "DebugLogFormat"
static let masksPrivateData = "MasksPrivateData"
2018-08-23 08:19:25 +00:00
}
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.prefersResolvedAddresses`
2018-08-23 08:19:25 +00:00
public let prefersResolvedAddresses: Bool
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.resolvedAddresses`
2018-08-23 08:19:25 +00:00
public let resolvedAddresses: [String]?
/// - Seealso: `SessionProxy.Configuration.endpointProtocols`
@available(*, deprecated)
public var endpointProtocols: [EndpointProtocol]?
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.mtu`
public let mtu: Int
2018-08-23 08:19:25 +00:00
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.sessionConfiguration`
public let sessionConfiguration: SessionProxy.Configuration
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.shouldDebug`
2018-08-23 08:19:25 +00:00
public let shouldDebug: Bool
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.debugLogFormat`
2018-08-23 08:19:25 +00:00
public let debugLogFormat: String?
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.masksPrivateData`
public let masksPrivateData: Bool?
2018-08-23 08:19:25 +00:00
// MARK: Shortcuts
static let debugLogFilename = "debug.log"
static let lastErrorKey = "TunnelKitLastError"
fileprivate static let dataCountKey = "TunnelKitDataCount"
/**
Returns the URL of the latest debug log.
- Parameter in: The app group where to locate the log file.
- Returns: The URL of the debug log, if any.
*/
public func urlForLog(in appGroup: String) -> URL? {
guard shouldDebug else {
2018-08-23 08:19:25 +00:00
return nil
}
guard let parentURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
return nil
}
return parentURL.appendingPathComponent(Configuration.debugLogFilename)
}
/**
Returns the content of the latest debug log.
- Parameter in: The app group where to locate the log file.
- Returns: The content of the debug log, if any.
*/
public func existingLog(in appGroup: String) -> String? {
guard let url = urlForLog(in: appGroup) else {
return nil
}
return try? String(contentsOf: url)
2018-08-23 08:19:25 +00:00
}
/**
Returns the last error reported by the tunnel, if any.
- Parameter in: The app group where to locate the error key.
- Returns: The last tunnel error, if any.
*/
public func lastError(in appGroup: String) -> ProviderError? {
guard let rawValue = UserDefaults(suiteName: appGroup)?.string(forKey: Configuration.lastErrorKey) else {
return nil
}
return ProviderError(rawValue: rawValue)
}
/**
Clear the last error status.
- Parameter in: The app group where to locate the error key.
*/
public func clearLastError(in appGroup: String) {
UserDefaults(suiteName: appGroup)?.removeObject(forKey: Configuration.lastErrorKey)
}
/**
Returns the most recent (received, sent) count in bytes.
- Parameter in: The app group where to locate the count pair.
- Returns: The bytes count pair, if any.
*/
public func dataCount(in appGroup: String) -> (Int, Int)? {
guard let rawValue = UserDefaults(suiteName: appGroup)?.dataCountArray else {
return nil
}
guard rawValue.count == 2 else {
return nil
}
return (rawValue[0], rawValue[1])
}
2018-08-23 08:19:25 +00:00
// MARK: API
/**
Parses the app group from a provider configuration map.
- Parameter from: The map to parse.
- Returns: The parsed app group.
- Throws: `ProviderError.configuration` if `providerConfiguration` does not contain an app group.
*/
public static func appGroup(from providerConfiguration: [String: Any]) throws -> String {
guard let appGroup = providerConfiguration[Keys.appGroup] as? String else {
2018-10-21 10:58:36 +00:00
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(Keys.appGroup)]")
}
return appGroup
}
2018-08-23 08:19:25 +00:00
/**
Parses a new `TunnelKitProvider.Configuration` object from a provider configuration map.
2018-08-23 08:19:25 +00:00
- Parameter from: The map to parse.
- Returns: The parsed `TunnelKitProvider.Configuration` object.
2018-08-23 08:19:25 +00:00
- Throws: `ProviderError.configuration` if `providerConfiguration` is incomplete.
*/
public static func parsed(from providerConfiguration: [String: Any]) throws -> Configuration {
let builder = try ConfigurationBuilder(providerConfiguration: providerConfiguration)
return builder.build()
}
/**
Returns a dictionary representation of this configuration for use with `NETunnelProviderProtocol.providerConfiguration`.
- Parameter appGroup: The name of the app group in which the tunnel extension lives in.
2018-08-23 08:19:25 +00:00
- Returns: The dictionary representation of `self`.
*/
public func generatedProviderConfiguration(appGroup: String) -> [String: Any] {
2018-08-23 08:19:25 +00:00
let S = Keys.self
guard let ca = sessionConfiguration.ca else {
fatalError("No sessionConfiguration.ca set")
}
guard let endpointProtocols = sessionConfiguration.endpointProtocols else {
fatalError("No sessionConfiguration.endpointProtocols set")
}
2018-08-23 08:19:25 +00:00
var dict: [String: Any] = [
S.appGroup: appGroup,
S.prefersResolvedAddresses: prefersResolvedAddresses,
S.ca: ca.pem,
S.endpointProtocols: endpointProtocols.map { $0.rawValue },
2018-08-23 08:19:25 +00:00
S.mtu: mtu,
S.debug: shouldDebug
2018-08-23 08:19:25 +00:00
]
if let cipher = sessionConfiguration.cipher {
dict[S.cipherAlgorithm] = cipher.rawValue
}
if let digest = sessionConfiguration.digest {
dict[S.digestAlgorithm] = digest.rawValue
}
if let compressionFraming = sessionConfiguration.compressionFraming {
dict[S.compressionFraming] = compressionFraming.rawValue
}
if let compressionAlgorithm = sessionConfiguration.compressionAlgorithm {
dict[S.compressionAlgorithm] = compressionAlgorithm.rawValue
}
if let clientCertificate = sessionConfiguration.clientCertificate {
dict[S.clientCertificate] = clientCertificate.pem
}
if let clientKey = sessionConfiguration.clientKey {
dict[S.clientKey] = clientKey.pem
}
if let tlsWrapData = sessionConfiguration.tlsWrap?.serialized() {
dict[S.tlsWrap] = tlsWrapData
}
if let keepAliveSeconds = sessionConfiguration.keepAliveInterval {
dict[S.keepAlive] = keepAliveSeconds
}
if let renegotiatesAfterSeconds = sessionConfiguration.renegotiatesAfter {
2018-08-23 08:19:25 +00:00
dict[S.renegotiatesAfter] = renegotiatesAfterSeconds
}
if let checksEKU = sessionConfiguration.checksEKU {
dict[S.checksEKU] = checksEKU
}
if let randomizeEndpoint = sessionConfiguration.randomizeEndpoint {
dict[S.randomizeEndpoint] = randomizeEndpoint
}
if let usesPIAPatches = sessionConfiguration.usesPIAPatches {
dict[S.usesPIAPatches] = usesPIAPatches
}
if let dnsServers = sessionConfiguration.dnsServers {
dict[S.dnsServers] = dnsServers
}
if let searchDomain = sessionConfiguration.searchDomain {
dict[S.searchDomain] = searchDomain
}
//
if let resolvedAddresses = resolvedAddresses {
dict[S.resolvedAddresses] = resolvedAddresses
}
2018-08-23 08:19:25 +00:00
if let debugLogFormat = debugLogFormat {
dict[S.debugLogFormat] = debugLogFormat
}
if let masksPrivateData = masksPrivateData {
dict[S.masksPrivateData] = masksPrivateData
}
2018-08-23 08:19:25 +00:00
return dict
}
/**
Generates a `NETunnelProviderProtocol` from this configuration.
- Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension.
- Parameter appGroup: The name of the app group in which the tunnel extension lives in.
- Parameter hostname: The hostname the tunnel will connect to.
- Parameter credentials: The optional credentials to authenticate with.
2018-08-23 08:19:25 +00:00
- Returns: The generated `NETunnelProviderProtocol` object.
- Throws: `ProviderError.credentials` if unable to store `credentials.password` to the `appGroup` keychain.
2018-08-23 08:19:25 +00:00
*/
public func generatedTunnelProtocol(withBundleIdentifier bundleIdentifier: String, appGroup: String, hostname: String?, credentials: SessionProxy.Credentials? = nil) throws -> NETunnelProviderProtocol {
2018-08-23 08:19:25 +00:00
let protocolConfiguration = NETunnelProviderProtocol()
protocolConfiguration.providerBundleIdentifier = bundleIdentifier
protocolConfiguration.serverAddress = hostname
if let username = credentials?.username, let password = credentials?.password {
2018-10-05 15:45:05 +00:00
let keychain = Keychain(group: appGroup)
do {
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)
} catch _ {
2018-10-21 10:58:36 +00:00
throw ProviderConfigurationError.credentials(details: "keychain.set()")
2018-10-05 15:45:05 +00:00
}
protocolConfiguration.username = username
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username)
}
protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup)
2018-08-23 08:19:25 +00:00
return protocolConfiguration
}
func print(appVersion: String?) {
guard let endpointProtocols = sessionConfiguration.endpointProtocols else {
fatalError("No sessionConfiguration.endpointProtocols set")
}
2018-08-23 08:19:25 +00:00
if let appVersion = appVersion {
log.info("App version: \(appVersion)")
}
log.info("\tProtocols: \(endpointProtocols)")
log.info("\tCipher: \(sessionConfiguration.fallbackCipher)")
log.info("\tDigest: \(sessionConfiguration.fallbackDigest)")
log.info("\tCompression framing: \(sessionConfiguration.fallbackCompressionFraming)")
if let compressionAlgorithm = sessionConfiguration.compressionAlgorithm, compressionAlgorithm != .disabled {
log.info("\tCompression algorithm: \(compressionAlgorithm)")
} else {
log.info("\tCompression algorithm: disabled")
}
if let _ = sessionConfiguration.clientCertificate {
log.info("\tClient verification: enabled")
} else {
log.info("\tClient verification: disabled")
}
if let tlsWrap = sessionConfiguration.tlsWrap {
log.info("\tTLS wrapping: \(tlsWrap.strategy)")
} else {
log.info("\tTLS wrapping: disabled")
}
if let keepAliveSeconds = sessionConfiguration.keepAliveInterval, keepAliveSeconds > 0 {
log.info("\tKeep-alive: \(keepAliveSeconds) seconds")
} else {
log.info("\tKeep-alive: never")
}
if let renegotiatesAfterSeconds = sessionConfiguration.renegotiatesAfter, renegotiatesAfterSeconds > 0 {
log.info("\tRenegotiation: \(renegotiatesAfterSeconds) seconds")
2018-08-23 08:19:25 +00:00
} else {
log.info("\tRenegotiation: never")
2018-08-23 08:19:25 +00:00
}
if sessionConfiguration.checksEKU ?? false {
log.info("\tServer EKU verification: enabled")
} else {
log.info("\tServer EKU verification: disabled")
}
if sessionConfiguration.randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
if let dnsServers = sessionConfiguration.dnsServers {
log.info("\tDNS servers: \(dnsServers.maskedDescription)")
}
if let searchDomain = sessionConfiguration.searchDomain {
log.info("\tSearch domain: \(searchDomain.maskedDescription)")
}
log.info("\tMTU: \(mtu)")
log.info("\tDebug: \(shouldDebug)")
2019-03-21 18:40:37 +00:00
log.info("\tMasks private data: \(masksPrivateData ?? true)")
2018-08-23 08:19:25 +00:00
}
}
}
// MARK: Modification
extension TunnelKitProvider.Configuration {
2018-08-23 08:19:25 +00:00
/**
Returns a `TunnelKitProvider.ConfigurationBuilder` to use this configuration as a starting point for a new one.
2018-08-23 08:19:25 +00:00
- Returns: An editable `TunnelKitProvider.ConfigurationBuilder` initialized with this configuration.
2018-08-23 08:19:25 +00:00
*/
public func builder() -> TunnelKitProvider.ConfigurationBuilder {
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionConfiguration)
2018-08-23 08:19:25 +00:00
builder.mtu = mtu
builder.shouldDebug = shouldDebug
builder.debugLogFormat = debugLogFormat
2018-08-23 08:19:25 +00:00
return builder
}
}
/// :nodoc:
public extension UserDefaults {
2019-03-30 19:16:04 +00:00
@objc var dataCountArray: [Int]? {
get {
return array(forKey: TunnelKitProvider.Configuration.dataCountKey) as? [Int]
}
set {
set(newValue, forKey: TunnelKitProvider.Configuration.dataCountKey)
}
}
2019-03-30 19:16:04 +00:00
func removeDataCountArray() {
removeObject(forKey: TunnelKitProvider.Configuration.dataCountKey)
}
}