Merge pull request #83 from keeshux/merge-configuration-options
Merge configuration options
This commit is contained in:
commit
394ee1b56a
|
@ -22,8 +22,7 @@ custom_categories:
|
||||||
- StaticKey
|
- StaticKey
|
||||||
- SessionProxy
|
- SessionProxy
|
||||||
- SessionProxyDelegate
|
- SessionProxyDelegate
|
||||||
- OptionsBundle
|
- ConfigurationError
|
||||||
- OptionsError
|
|
||||||
- SessionReply
|
- SessionReply
|
||||||
- IPv4Settings
|
- IPv4Settings
|
||||||
- IPv6Settings
|
- IPv6Settings
|
||||||
|
|
|
@ -88,15 +88,16 @@ extension ViewController {
|
||||||
let port = UInt16(textPort.text!)!
|
let port = UInt16(textPort.text!)!
|
||||||
let credentials = SessionProxy.Credentials(textUsername.text!, textPassword.text!)
|
let credentials = SessionProxy.Credentials(textUsername.text!, textPassword.text!)
|
||||||
|
|
||||||
var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca)
|
var sessionBuilder = SessionProxy.ConfigurationBuilder()
|
||||||
sessionBuilder.cipher = .aes256gcm
|
sessionBuilder.ca = ca
|
||||||
|
sessionBuilder.cipher = .aes128cbc
|
||||||
sessionBuilder.digest = .sha1
|
sessionBuilder.digest = .sha1
|
||||||
sessionBuilder.compressionFraming = .compLZO
|
sessionBuilder.compressionFraming = .compLZO
|
||||||
sessionBuilder.renegotiatesAfter = nil
|
sessionBuilder.renegotiatesAfter = nil
|
||||||
|
let socketType: SocketType = switchTCP.isOn ? .tcp : .udp
|
||||||
|
sessionBuilder.endpointProtocols = [EndpointProtocol(socketType, port)]
|
||||||
sessionBuilder.usesPIAPatches = true
|
sessionBuilder.usesPIAPatches = true
|
||||||
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
||||||
let socketType: SocketType = switchTCP.isOn ? .tcp : .udp
|
|
||||||
builder.endpointProtocols = [EndpointProtocol(socketType, port)]
|
|
||||||
builder.mtu = 1350
|
builder.mtu = 1350
|
||||||
builder.shouldDebug = true
|
builder.shouldDebug = true
|
||||||
builder.masksPrivateData = false
|
builder.masksPrivateData = false
|
||||||
|
|
|
@ -88,16 +88,17 @@ extension ViewController {
|
||||||
let port = UInt16(textPort.stringValue)!
|
let port = UInt16(textPort.stringValue)!
|
||||||
let credentials = SessionProxy.Credentials(textUsername.stringValue, textPassword.stringValue)
|
let credentials = SessionProxy.Credentials(textUsername.stringValue, textPassword.stringValue)
|
||||||
|
|
||||||
var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca)
|
var sessionBuilder = SessionProxy.ConfigurationBuilder()
|
||||||
|
sessionBuilder.ca = ca
|
||||||
sessionBuilder.cipher = .aes128cbc
|
sessionBuilder.cipher = .aes128cbc
|
||||||
sessionBuilder.digest = .sha1
|
sessionBuilder.digest = .sha1
|
||||||
sessionBuilder.compressionFraming = .compLZO
|
sessionBuilder.compressionFraming = .compLZO
|
||||||
sessionBuilder.renegotiatesAfter = nil
|
sessionBuilder.renegotiatesAfter = nil
|
||||||
sessionBuilder.usesPIAPatches = true
|
|
||||||
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
|
||||||
// let socketType: SocketType = isTCP ? .tcp : .udp
|
// let socketType: SocketType = isTCP ? .tcp : .udp
|
||||||
let socketType: SocketType = .udp
|
let socketType: SocketType = .udp
|
||||||
builder.endpointProtocols = [EndpointProtocol(socketType, port)]
|
sessionBuilder.endpointProtocols = [EndpointProtocol(socketType, port)]
|
||||||
|
sessionBuilder.usesPIAPatches = true
|
||||||
|
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
||||||
builder.mtu = 1350
|
builder.mtu = 1350
|
||||||
builder.shouldDebug = true
|
builder.shouldDebug = true
|
||||||
builder.masksPrivateData = false
|
builder.masksPrivateData = false
|
||||||
|
|
|
@ -137,12 +137,8 @@
|
||||||
0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */; };
|
0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */; };
|
||||||
0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; };
|
0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; };
|
||||||
0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; };
|
0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; };
|
||||||
0ECC60D5225497400020BEAC /* OptionsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D4225497400020BEAC /* OptionsBundle.swift */; };
|
0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */; };
|
||||||
0ECC60D6225497400020BEAC /* OptionsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D4225497400020BEAC /* OptionsBundle.swift */; };
|
0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* ConfigurationError.swift */; };
|
||||||
0ECC60D82254981A0020BEAC /* OptionsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* OptionsError.swift */; };
|
|
||||||
0ECC60D92254981A0020BEAC /* OptionsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60D72254981A0020BEAC /* OptionsError.swift */; };
|
|
||||||
0ECC60DB2254C8190020BEAC /* OptionsBundleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DA2254C8190020BEAC /* OptionsBundleTests.swift */; };
|
|
||||||
0ECC60DC2254C8190020BEAC /* OptionsBundleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DA2254C8190020BEAC /* OptionsBundleTests.swift */; };
|
|
||||||
0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; };
|
0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; };
|
||||||
0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; };
|
0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; };
|
||||||
0ECEB1152252C8E900E9E551 /* tunnelbear.enc.8.ovpn in Resources */ = {isa = PBXBuildFile; fileRef = 0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */; };
|
0ECEB1152252C8E900E9E551 /* tunnelbear.enc.8.ovpn in Resources */ = {isa = PBXBuildFile; fileRef = 0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */; };
|
||||||
|
@ -348,9 +344,7 @@
|
||||||
0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWTCPConnectionState+Description.swift"; sourceTree = "<group>"; };
|
0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWTCPConnectionState+Description.swift"; sourceTree = "<group>"; };
|
||||||
0EC1BBA420D71190007C4C7B /* DNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = "<group>"; };
|
0EC1BBA420D71190007C4C7B /* DNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = "<group>"; };
|
||||||
0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = "<group>"; };
|
0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = "<group>"; };
|
||||||
0ECC60D4225497400020BEAC /* OptionsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsBundle.swift; sourceTree = "<group>"; };
|
0ECC60D72254981A0020BEAC /* ConfigurationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationError.swift; sourceTree = "<group>"; };
|
||||||
0ECC60D72254981A0020BEAC /* OptionsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsError.swift; sourceTree = "<group>"; };
|
|
||||||
0ECC60DA2254C8190020BEAC /* OptionsBundleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsBundleTests.swift; sourceTree = "<group>"; };
|
|
||||||
0ECE3527212EB7770040F253 /* CryptoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoContainer.swift; sourceTree = "<group>"; };
|
0ECE3527212EB7770040F253 /* CryptoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoContainer.swift; sourceTree = "<group>"; };
|
||||||
0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.ovpn; sourceTree = "<group>"; };
|
0ECEB1132252C8E900E9E551 /* tunnelbear.enc.8.ovpn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.ovpn; sourceTree = "<group>"; };
|
||||||
0ECEB1142252C8E900E9E551 /* tunnelbear.enc.8.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.key; sourceTree = "<group>"; };
|
0ECEB1142252C8E900E9E551 /* tunnelbear.enc.8.key */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tunnelbear.enc.8.key; sourceTree = "<group>"; };
|
||||||
|
@ -473,7 +467,6 @@
|
||||||
0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */,
|
0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */,
|
||||||
0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */,
|
0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */,
|
||||||
0EB2B45820F0BD9A004233D7 /* LinkTests.swift */,
|
0EB2B45820F0BD9A004233D7 /* LinkTests.swift */,
|
||||||
0ECC60DA2254C8190020BEAC /* OptionsBundleTests.swift */,
|
|
||||||
0E12B2A22145341B00B4BAE9 /* PacketTests.swift */,
|
0E12B2A22145341B00B4BAE9 /* PacketTests.swift */,
|
||||||
0E245D682135972800B012A2 /* PushTests.swift */,
|
0E245D682135972800B012A2 /* PushTests.swift */,
|
||||||
0EB2B45620F0BD16004233D7 /* RandomTests.swift */,
|
0EB2B45620F0BD16004233D7 /* RandomTests.swift */,
|
||||||
|
@ -624,6 +617,7 @@
|
||||||
0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */,
|
0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */,
|
||||||
0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */,
|
0E58BF4F2240F98E006FB157 /* CompressionAlgorithmNative.h */,
|
||||||
0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */,
|
0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */,
|
||||||
|
0ECC60D72254981A0020BEAC /* ConfigurationError.swift */,
|
||||||
0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */,
|
0E011F872196E2AB00BA59EE /* ConfigurationParser.swift */,
|
||||||
0E39BCE6214B2AB60035E9DE /* ControlPacket.h */,
|
0E39BCE6214B2AB60035E9DE /* ControlPacket.h */,
|
||||||
0E39BCE7214B2AB60035E9DE /* ControlPacket.m */,
|
0E39BCE7214B2AB60035E9DE /* ControlPacket.m */,
|
||||||
|
@ -656,8 +650,6 @@
|
||||||
0EFEB42D2006D3C800F81029 /* MSS.h */,
|
0EFEB42D2006D3C800F81029 /* MSS.h */,
|
||||||
0EFEB43D2006D3C800F81029 /* MSS.m */,
|
0EFEB43D2006D3C800F81029 /* MSS.m */,
|
||||||
0E12B29D21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift */,
|
0E12B29D21449ADB00B4BAE9 /* NSRegularExpression+Shortcuts.swift */,
|
||||||
0ECC60D4225497400020BEAC /* OptionsBundle.swift */,
|
|
||||||
0ECC60D72254981A0020BEAC /* OptionsError.swift */,
|
|
||||||
0EFEB43E2006D3C800F81029 /* Packet.swift */,
|
0EFEB43E2006D3C800F81029 /* Packet.swift */,
|
||||||
0EE7A79420F61EDC00B42E6A /* PacketMacros.h */,
|
0EE7A79420F61EDC00B42E6A /* PacketMacros.h */,
|
||||||
0EE7A79720F6296F00B42E6A /* PacketMacros.m */,
|
0EE7A79720F6296F00B42E6A /* PacketMacros.m */,
|
||||||
|
@ -1155,7 +1147,6 @@
|
||||||
files = (
|
files = (
|
||||||
0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */,
|
0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */,
|
||||||
0E011F812196E23700BA59EE /* ConfigurationParserTests.swift in Sources */,
|
0E011F812196E23700BA59EE /* ConfigurationParserTests.swift in Sources */,
|
||||||
0ECC60DB2254C8190020BEAC /* OptionsBundleTests.swift in Sources */,
|
|
||||||
0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */,
|
0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */,
|
||||||
0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */,
|
0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */,
|
||||||
0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */,
|
0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */,
|
||||||
|
@ -1238,7 +1229,7 @@
|
||||||
0EFEB4722006D3C800F81029 /* ReplayProtector.m in Sources */,
|
0EFEB4722006D3C800F81029 /* ReplayProtector.m in Sources */,
|
||||||
0EFEB4782006D3C800F81029 /* TunnelKitProvider+Configuration.swift in Sources */,
|
0EFEB4782006D3C800F81029 /* TunnelKitProvider+Configuration.swift in Sources */,
|
||||||
0E3E0F212108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */,
|
0E3E0F212108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */,
|
||||||
0ECC60D82254981A0020BEAC /* OptionsError.swift in Sources */,
|
0ECC60D82254981A0020BEAC /* ConfigurationError.swift in Sources */,
|
||||||
0EFEB4752006D3C800F81029 /* Errors.m in Sources */,
|
0EFEB4752006D3C800F81029 /* Errors.m in Sources */,
|
||||||
0E58BF532240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */,
|
0E58BF532240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */,
|
||||||
0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */,
|
0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */,
|
||||||
|
@ -1246,7 +1237,6 @@
|
||||||
0EFEB4762006D3C800F81029 /* DataPath.m in Sources */,
|
0EFEB4762006D3C800F81029 /* DataPath.m in Sources */,
|
||||||
0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */,
|
0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */,
|
||||||
0EFEB4692006D3C800F81029 /* Packet.swift in Sources */,
|
0EFEB4692006D3C800F81029 /* Packet.swift in Sources */,
|
||||||
0ECC60D5225497400020BEAC /* OptionsBundle.swift in Sources */,
|
|
||||||
0E011F7A2196D93600BA59EE /* SocketType.swift in Sources */,
|
0E011F7A2196D93600BA59EE /* SocketType.swift in Sources */,
|
||||||
0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */,
|
0EFEB45A2006D3C800F81029 /* TunnelInterface.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -1307,7 +1297,7 @@
|
||||||
0EFEB4AF2007627700F81029 /* InterfaceObserver.swift in Sources */,
|
0EFEB4AF2007627700F81029 /* InterfaceObserver.swift in Sources */,
|
||||||
0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */,
|
0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */,
|
||||||
0EBBF2E62084FE6F00E36B40 /* GenericSocket.swift in Sources */,
|
0EBBF2E62084FE6F00E36B40 /* GenericSocket.swift in Sources */,
|
||||||
0ECC60D92254981A0020BEAC /* OptionsError.swift in Sources */,
|
0ECC60D92254981A0020BEAC /* ConfigurationError.swift in Sources */,
|
||||||
0E3E0F222108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */,
|
0E3E0F222108A8CC00B371C1 /* SessionProxy+SessionReply.swift in Sources */,
|
||||||
0E58BF542240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */,
|
0E58BF542240FAA6006FB157 /* SessionProxy+CompressionAlgorithm.swift in Sources */,
|
||||||
0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */,
|
0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */,
|
||||||
|
@ -1315,7 +1305,6 @@
|
||||||
0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */,
|
0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */,
|
||||||
0E0C2128212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */,
|
0E0C2128212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */,
|
||||||
0EFEB4972006D7F300F81029 /* SessionProxy+Authenticator.swift in Sources */,
|
0EFEB4972006D7F300F81029 /* SessionProxy+Authenticator.swift in Sources */,
|
||||||
0ECC60D6225497400020BEAC /* OptionsBundle.swift in Sources */,
|
|
||||||
0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */,
|
0E011F7B2196D93600BA59EE /* SocketType.swift in Sources */,
|
||||||
0EFEB49B2006D7F300F81029 /* Packet.swift in Sources */,
|
0EFEB49B2006D7F300F81029 /* Packet.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -1327,7 +1316,6 @@
|
||||||
files = (
|
files = (
|
||||||
0EA82A3A2190B2B9007960EB /* RandomTests.swift in Sources */,
|
0EA82A3A2190B2B9007960EB /* RandomTests.swift in Sources */,
|
||||||
0E011F822196E23800BA59EE /* ConfigurationParserTests.swift in Sources */,
|
0E011F822196E23800BA59EE /* ConfigurationParserTests.swift in Sources */,
|
||||||
0ECC60DC2254C8190020BEAC /* OptionsBundleTests.swift in Sources */,
|
|
||||||
0EA82A332190B2B9007960EB /* DataPathPerformanceTests.swift in Sources */,
|
0EA82A332190B2B9007960EB /* DataPathPerformanceTests.swift in Sources */,
|
||||||
0EA82A372190B2B9007960EB /* LinkTests.swift in Sources */,
|
0EA82A372190B2B9007960EB /* LinkTests.swift in Sources */,
|
||||||
0EA82A352190B2B9007960EB /* EncryptionPerformanceTests.swift in Sources */,
|
0EA82A352190B2B9007960EB /* EncryptionPerformanceTests.swift in Sources */,
|
||||||
|
|
|
@ -59,11 +59,13 @@ class ConnectionStrategy {
|
||||||
prefersResolvedAddresses = configuration.prefersResolvedAddresses
|
prefersResolvedAddresses = configuration.prefersResolvedAddresses
|
||||||
resolvedAddresses = configuration.resolvedAddresses
|
resolvedAddresses = configuration.resolvedAddresses
|
||||||
|
|
||||||
if configuration.sessionConfiguration.randomizeEndpoint ?? false {
|
guard var endpointProtocols = configuration.sessionConfiguration.endpointProtocols else {
|
||||||
endpointProtocols = configuration.endpointProtocols.shuffled()
|
fatalError("No endpoints defined")
|
||||||
} else {
|
|
||||||
endpointProtocols = configuration.endpointProtocols
|
|
||||||
}
|
}
|
||||||
|
if configuration.sessionConfiguration.randomizeEndpoint ?? false {
|
||||||
|
endpointProtocols.shuffle()
|
||||||
|
}
|
||||||
|
self.endpointProtocols = endpointProtocols
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSocket(
|
func createSocket(
|
||||||
|
|
|
@ -52,25 +52,9 @@ extension TunnelKitProvider {
|
||||||
public static let defaults = Configuration(
|
public static let defaults = Configuration(
|
||||||
prefersResolvedAddresses: false,
|
prefersResolvedAddresses: false,
|
||||||
resolvedAddresses: nil,
|
resolvedAddresses: nil,
|
||||||
endpointProtocols: [EndpointProtocol(.udp, 1194)],
|
endpointProtocols: nil,
|
||||||
mtu: 1250,
|
mtu: 1250,
|
||||||
sessionConfiguration: SessionProxy.Configuration(
|
sessionConfiguration: SessionProxy.ConfigurationBuilder().build(),
|
||||||
cipher: .aes128cbc,
|
|
||||||
digest: .sha1,
|
|
||||||
ca: CryptoContainer(pem: ""),
|
|
||||||
clientCertificate: nil,
|
|
||||||
clientKey: nil,
|
|
||||||
checksEKU: false,
|
|
||||||
compressionFraming: .disabled,
|
|
||||||
compressionAlgorithm: .disabled,
|
|
||||||
tlsWrap: nil,
|
|
||||||
keepAliveInterval: nil,
|
|
||||||
renegotiatesAfter: nil,
|
|
||||||
dnsServers: nil,
|
|
||||||
searchDomain: nil,
|
|
||||||
randomizeEndpoint: false,
|
|
||||||
usesPIAPatches: nil
|
|
||||||
),
|
|
||||||
shouldDebug: false,
|
shouldDebug: false,
|
||||||
debugLogFormat: nil,
|
debugLogFormat: nil,
|
||||||
masksPrivateData: true
|
masksPrivateData: true
|
||||||
|
@ -84,9 +68,6 @@ extension TunnelKitProvider {
|
||||||
/// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true`.
|
/// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true`.
|
||||||
public var resolvedAddresses: [String]?
|
public var resolvedAddresses: [String]?
|
||||||
|
|
||||||
/// The accepted communication protocols. Must be non-empty.
|
|
||||||
public var endpointProtocols: [EndpointProtocol]
|
|
||||||
|
|
||||||
/// The MTU of the link.
|
/// The MTU of the link.
|
||||||
public var mtu: Int
|
public var mtu: Int
|
||||||
|
|
||||||
|
@ -114,7 +95,6 @@ extension TunnelKitProvider {
|
||||||
public init(sessionConfiguration: SessionProxy.Configuration) {
|
public init(sessionConfiguration: SessionProxy.Configuration) {
|
||||||
prefersResolvedAddresses = ConfigurationBuilder.defaults.prefersResolvedAddresses
|
prefersResolvedAddresses = ConfigurationBuilder.defaults.prefersResolvedAddresses
|
||||||
resolvedAddresses = nil
|
resolvedAddresses = nil
|
||||||
endpointProtocols = ConfigurationBuilder.defaults.endpointProtocols
|
|
||||||
mtu = ConfigurationBuilder.defaults.mtu
|
mtu = ConfigurationBuilder.defaults.mtu
|
||||||
self.sessionConfiguration = sessionConfiguration
|
self.sessionConfiguration = sessionConfiguration
|
||||||
shouldDebug = ConfigurationBuilder.defaults.shouldDebug
|
shouldDebug = ConfigurationBuilder.defaults.shouldDebug
|
||||||
|
@ -127,15 +107,6 @@ extension TunnelKitProvider {
|
||||||
|
|
||||||
prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? ConfigurationBuilder.defaults.prefersResolvedAddresses
|
prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? ConfigurationBuilder.defaults.prefersResolvedAddresses
|
||||||
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
|
resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String]
|
||||||
guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else {
|
|
||||||
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.endpointProtocols)] is nil or empty")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
mtu = providerConfiguration[S.mtu] as? Int ?? ConfigurationBuilder.defaults.mtu
|
mtu = providerConfiguration[S.mtu] as? Int ?? ConfigurationBuilder.defaults.mtu
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -166,7 +137,8 @@ extension TunnelKitProvider {
|
||||||
clientKey = nil
|
clientKey = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionConfigurationBuilder = SessionProxy.ConfigurationBuilder(ca: ca)
|
var sessionConfigurationBuilder = SessionProxy.ConfigurationBuilder()
|
||||||
|
sessionConfigurationBuilder.ca = ca
|
||||||
sessionConfigurationBuilder.cipher = cipher
|
sessionConfigurationBuilder.cipher = cipher
|
||||||
sessionConfigurationBuilder.digest = digest
|
sessionConfigurationBuilder.digest = digest
|
||||||
sessionConfigurationBuilder.clientCertificate = clientCertificate
|
sessionConfigurationBuilder.clientCertificate = clientCertificate
|
||||||
|
@ -190,6 +162,15 @@ extension TunnelKitProvider {
|
||||||
}
|
}
|
||||||
sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.keepAliveInterval
|
sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.keepAliveInterval
|
||||||
sessionConfigurationBuilder.renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.renegotiatesAfter
|
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.checksEKU = providerConfiguration[S.checksEKU] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.checksEKU
|
||||||
sessionConfigurationBuilder.dnsServers = providerConfiguration[S.dnsServers] as? [String]
|
sessionConfigurationBuilder.dnsServers = providerConfiguration[S.dnsServers] as? [String]
|
||||||
sessionConfigurationBuilder.searchDomain = providerConfiguration[S.searchDomain] as? String
|
sessionConfigurationBuilder.searchDomain = providerConfiguration[S.searchDomain] as? String
|
||||||
|
@ -217,7 +198,7 @@ extension TunnelKitProvider {
|
||||||
return Configuration(
|
return Configuration(
|
||||||
prefersResolvedAddresses: prefersResolvedAddresses,
|
prefersResolvedAddresses: prefersResolvedAddresses,
|
||||||
resolvedAddresses: resolvedAddresses,
|
resolvedAddresses: resolvedAddresses,
|
||||||
endpointProtocols: endpointProtocols,
|
endpointProtocols: nil,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
sessionConfiguration: sessionConfiguration,
|
sessionConfiguration: sessionConfiguration,
|
||||||
shouldDebug: shouldDebug,
|
shouldDebug: shouldDebug,
|
||||||
|
@ -236,8 +217,6 @@ extension TunnelKitProvider {
|
||||||
|
|
||||||
static let resolvedAddresses = "ResolvedAddresses"
|
static let resolvedAddresses = "ResolvedAddresses"
|
||||||
|
|
||||||
static let endpointProtocols = "EndpointProtocols"
|
|
||||||
|
|
||||||
static let mtu = "MTU"
|
static let mtu = "MTU"
|
||||||
|
|
||||||
// MARK: SessionConfiguration
|
// MARK: SessionConfiguration
|
||||||
|
@ -260,6 +239,8 @@ extension TunnelKitProvider {
|
||||||
|
|
||||||
static let keepAlive = "KeepAlive"
|
static let keepAlive = "KeepAlive"
|
||||||
|
|
||||||
|
static let endpointProtocols = "EndpointProtocols"
|
||||||
|
|
||||||
static let renegotiatesAfter = "RenegotiatesAfter"
|
static let renegotiatesAfter = "RenegotiatesAfter"
|
||||||
|
|
||||||
static let checksEKU = "ChecksEKU"
|
static let checksEKU = "ChecksEKU"
|
||||||
|
@ -287,8 +268,9 @@ extension TunnelKitProvider {
|
||||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.resolvedAddresses`
|
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.resolvedAddresses`
|
||||||
public let resolvedAddresses: [String]?
|
public let resolvedAddresses: [String]?
|
||||||
|
|
||||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.endpointProtocols`
|
/// - Seealso: `SessionProxy.Configuration.endpointProtocols`
|
||||||
public let endpointProtocols: [EndpointProtocol]
|
@available(*, deprecated)
|
||||||
|
public var endpointProtocols: [EndpointProtocol]?
|
||||||
|
|
||||||
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.mtu`
|
/// - Seealso: `TunnelKitProvider.ConfigurationBuilder.mtu`
|
||||||
public let mtu: Int
|
public let mtu: Int
|
||||||
|
@ -417,29 +399,39 @@ extension TunnelKitProvider {
|
||||||
public func generatedProviderConfiguration(appGroup: String) -> [String: Any] {
|
public func generatedProviderConfiguration(appGroup: String) -> [String: Any] {
|
||||||
let S = Keys.self
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
var dict: [String: Any] = [
|
var dict: [String: Any] = [
|
||||||
S.appGroup: appGroup,
|
S.appGroup: appGroup,
|
||||||
S.prefersResolvedAddresses: prefersResolvedAddresses,
|
S.prefersResolvedAddresses: prefersResolvedAddresses,
|
||||||
|
S.ca: ca.pem,
|
||||||
S.endpointProtocols: endpointProtocols.map { $0.rawValue },
|
S.endpointProtocols: endpointProtocols.map { $0.rawValue },
|
||||||
S.cipherAlgorithm: sessionConfiguration.cipher.rawValue,
|
|
||||||
S.digestAlgorithm: sessionConfiguration.digest.rawValue,
|
|
||||||
S.ca: sessionConfiguration.ca.pem,
|
|
||||||
S.mtu: mtu,
|
S.mtu: mtu,
|
||||||
S.debug: shouldDebug
|
S.debug: shouldDebug
|
||||||
]
|
]
|
||||||
|
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 {
|
if let clientCertificate = sessionConfiguration.clientCertificate {
|
||||||
dict[S.clientCertificate] = clientCertificate.pem
|
dict[S.clientCertificate] = clientCertificate.pem
|
||||||
}
|
}
|
||||||
if let clientKey = sessionConfiguration.clientKey {
|
if let clientKey = sessionConfiguration.clientKey {
|
||||||
dict[S.clientKey] = clientKey.pem
|
dict[S.clientKey] = clientKey.pem
|
||||||
}
|
}
|
||||||
if let resolvedAddresses = resolvedAddresses {
|
|
||||||
dict[S.resolvedAddresses] = resolvedAddresses
|
|
||||||
}
|
|
||||||
dict[S.compressionFraming] = sessionConfiguration.compressionFraming.rawValue
|
|
||||||
if let compressionAlgorithm = sessionConfiguration.compressionAlgorithm?.rawValue {
|
|
||||||
dict[S.compressionAlgorithm] = compressionAlgorithm
|
|
||||||
}
|
|
||||||
if let tlsWrapData = sessionConfiguration.tlsWrap?.serialized() {
|
if let tlsWrapData = sessionConfiguration.tlsWrap?.serialized() {
|
||||||
dict[S.tlsWrap] = tlsWrapData
|
dict[S.tlsWrap] = tlsWrapData
|
||||||
}
|
}
|
||||||
|
@ -452,17 +444,21 @@ extension TunnelKitProvider {
|
||||||
if let checksEKU = sessionConfiguration.checksEKU {
|
if let checksEKU = sessionConfiguration.checksEKU {
|
||||||
dict[S.checksEKU] = 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 {
|
if let dnsServers = sessionConfiguration.dnsServers {
|
||||||
dict[S.dnsServers] = dnsServers
|
dict[S.dnsServers] = dnsServers
|
||||||
}
|
}
|
||||||
if let searchDomain = sessionConfiguration.searchDomain {
|
if let searchDomain = sessionConfiguration.searchDomain {
|
||||||
dict[S.searchDomain] = searchDomain
|
dict[S.searchDomain] = searchDomain
|
||||||
}
|
}
|
||||||
if let randomizeEndpoint = sessionConfiguration.randomizeEndpoint {
|
//
|
||||||
dict[S.randomizeEndpoint] = randomizeEndpoint
|
if let resolvedAddresses = resolvedAddresses {
|
||||||
}
|
dict[S.resolvedAddresses] = resolvedAddresses
|
||||||
if let usesPIAPatches = sessionConfiguration.usesPIAPatches {
|
|
||||||
dict[S.usesPIAPatches] = usesPIAPatches
|
|
||||||
}
|
}
|
||||||
if let debugLogFormat = debugLogFormat {
|
if let debugLogFormat = debugLogFormat {
|
||||||
dict[S.debugLogFormat] = debugLogFormat
|
dict[S.debugLogFormat] = debugLogFormat
|
||||||
|
@ -504,29 +500,32 @@ extension TunnelKitProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func print(appVersion: String?) {
|
func print(appVersion: String?) {
|
||||||
|
guard let endpointProtocols = sessionConfiguration.endpointProtocols else {
|
||||||
|
fatalError("No sessionConfiguration.endpointProtocols set")
|
||||||
|
}
|
||||||
|
|
||||||
if let appVersion = appVersion {
|
if let appVersion = appVersion {
|
||||||
log.info("App version: \(appVersion)")
|
log.info("App version: \(appVersion)")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("\tProtocols: \(endpointProtocols)")
|
log.info("\tProtocols: \(endpointProtocols)")
|
||||||
log.info("\tCipher: \(sessionConfiguration.cipher)")
|
log.info("\tCipher: \(sessionConfiguration.fallbackCipher)")
|
||||||
log.info("\tDigest: \(sessionConfiguration.digest)")
|
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 {
|
if let _ = sessionConfiguration.clientCertificate {
|
||||||
log.info("\tClient verification: enabled")
|
log.info("\tClient verification: enabled")
|
||||||
} else {
|
} else {
|
||||||
log.info("\tClient verification: disabled")
|
log.info("\tClient verification: disabled")
|
||||||
}
|
}
|
||||||
if sessionConfiguration.checksEKU ?? false {
|
if let tlsWrap = sessionConfiguration.tlsWrap {
|
||||||
log.info("\tServer EKU verification: enabled")
|
log.info("\tTLS wrapping: \(tlsWrap.strategy)")
|
||||||
} else {
|
} else {
|
||||||
log.info("\tServer EKU verification: disabled")
|
log.info("\tTLS wrapping: disabled")
|
||||||
}
|
|
||||||
log.info("\tMTU: \(mtu)")
|
|
||||||
log.info("\tCompression framing: \(sessionConfiguration.compressionFraming)")
|
|
||||||
if let compressionAlgorithm = sessionConfiguration.compressionAlgorithm, compressionAlgorithm != .disabled {
|
|
||||||
log.info("\tCompression algorithm: \(compressionAlgorithm)")
|
|
||||||
} else {
|
|
||||||
log.info("\tCompression algorithm: disabled")
|
|
||||||
}
|
}
|
||||||
if let keepAliveSeconds = sessionConfiguration.keepAliveInterval, keepAliveSeconds > 0 {
|
if let keepAliveSeconds = sessionConfiguration.keepAliveInterval, keepAliveSeconds > 0 {
|
||||||
log.info("\tKeep-alive: \(keepAliveSeconds) seconds")
|
log.info("\tKeep-alive: \(keepAliveSeconds) seconds")
|
||||||
|
@ -538,20 +537,21 @@ extension TunnelKitProvider {
|
||||||
} else {
|
} else {
|
||||||
log.info("\tRenegotiation: never")
|
log.info("\tRenegotiation: never")
|
||||||
}
|
}
|
||||||
if let tlsWrap = sessionConfiguration.tlsWrap {
|
if sessionConfiguration.checksEKU ?? false {
|
||||||
log.info("\tTLS wrapping: \(tlsWrap.strategy)")
|
log.info("\tServer EKU verification: enabled")
|
||||||
} else {
|
} else {
|
||||||
log.info("\tTLS wrapping: disabled")
|
log.info("\tServer EKU verification: disabled")
|
||||||
}
|
|
||||||
if let dnsServers = sessionConfiguration.dnsServers {
|
|
||||||
log.info("\tCustom DNS servers: \(dnsServers.maskedDescription)")
|
|
||||||
}
|
|
||||||
if let searchDomain = sessionConfiguration.searchDomain {
|
|
||||||
log.info("\tCustom search domain: \(searchDomain.maskedDescription)")
|
|
||||||
}
|
}
|
||||||
if sessionConfiguration.randomizeEndpoint ?? false {
|
if sessionConfiguration.randomizeEndpoint ?? false {
|
||||||
log.info("\tRandomize endpoint: true")
|
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)")
|
log.info("\tDebug: \(shouldDebug)")
|
||||||
log.info("\tMasks private data: \(masksPrivateData ?? true)")
|
log.info("\tMasks private data: \(masksPrivateData ?? true)")
|
||||||
}
|
}
|
||||||
|
@ -560,7 +560,7 @@ extension TunnelKitProvider {
|
||||||
|
|
||||||
// MARK: Modification
|
// MARK: Modification
|
||||||
|
|
||||||
extension TunnelKitProvider.Configuration: Equatable {
|
extension TunnelKitProvider.Configuration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns a `TunnelKitProvider.ConfigurationBuilder` to use this configuration as a starting point for a new one.
|
Returns a `TunnelKitProvider.ConfigurationBuilder` to use this configuration as a starting point for a new one.
|
||||||
|
@ -569,38 +569,11 @@ extension TunnelKitProvider.Configuration: Equatable {
|
||||||
*/
|
*/
|
||||||
public func builder() -> TunnelKitProvider.ConfigurationBuilder {
|
public func builder() -> TunnelKitProvider.ConfigurationBuilder {
|
||||||
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionConfiguration)
|
var builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionConfiguration)
|
||||||
builder.endpointProtocols = endpointProtocols
|
|
||||||
builder.mtu = mtu
|
builder.mtu = mtu
|
||||||
builder.shouldDebug = shouldDebug
|
builder.shouldDebug = shouldDebug
|
||||||
builder.debugLogFormat = debugLogFormat
|
builder.debugLogFormat = debugLogFormat
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public static func ==(lhs: TunnelKitProvider.Configuration, rhs: TunnelKitProvider.Configuration) -> Bool {
|
|
||||||
return (
|
|
||||||
(lhs.endpointProtocols == rhs.endpointProtocols) &&
|
|
||||||
(lhs.mtu == rhs.mtu) &&
|
|
||||||
(lhs.sessionConfiguration == rhs.sessionConfiguration)
|
|
||||||
// XXX: tlsWrap not copied
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
extension EndpointProtocol: Codable {
|
|
||||||
public init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.singleValueContainer()
|
|
||||||
guard let proto = try EndpointProtocol(rawValue: container.decode(String.self)) else {
|
|
||||||
throw TunnelKitProvider.ProviderConfigurationError.parameter(name: "endpointProtocol.decodable")
|
|
||||||
}
|
|
||||||
self.init(proto.socketType, proto.port)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.singleValueContainer()
|
|
||||||
try container.encode(rawValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// :nodoc:
|
/// :nodoc:
|
||||||
|
|
|
@ -465,7 +465,11 @@ extension TunnelKitProvider: SessionProxyDelegate {
|
||||||
log.info("\tRemote: \(remoteAddress.maskedDescription)")
|
log.info("\tRemote: \(remoteAddress.maskedDescription)")
|
||||||
log.info("\tIPv4: \(reply.options.ipv4?.description ?? "not configured")")
|
log.info("\tIPv4: \(reply.options.ipv4?.description ?? "not configured")")
|
||||||
log.info("\tIPv6: \(reply.options.ipv6?.description ?? "not configured")")
|
log.info("\tIPv6: \(reply.options.ipv6?.description ?? "not configured")")
|
||||||
log.info("\tDNS: \(reply.options.dnsServers.map { $0.maskedDescription })")
|
if let dnsServers = reply.options.dnsServers {
|
||||||
|
log.info("\tDNS: \(dnsServers.map { $0.maskedDescription })")
|
||||||
|
} else {
|
||||||
|
log.info("\tDNS: not configured)")
|
||||||
|
}
|
||||||
log.info("\tDomain: \(reply.options.searchDomain?.maskedDescription ?? "not configured")")
|
log.info("\tDomain: \(reply.options.searchDomain?.maskedDescription ?? "not configured")")
|
||||||
|
|
||||||
bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in
|
bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in
|
||||||
|
@ -510,7 +514,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
|
||||||
var routes: [NEIPv4Route] = [defaultRoute]
|
var routes: [NEIPv4Route] = [defaultRoute]
|
||||||
for r in ipv4.routes {
|
for r in ipv4.routes {
|
||||||
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
|
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
|
||||||
ipv4Route.gatewayAddress = r.gateway ?? ipv4.defaultGateway
|
ipv4Route.gatewayAddress = r.gateway
|
||||||
routes.append(ipv4Route)
|
routes.append(ipv4Route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,7 +531,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
|
||||||
var routes: [NEIPv6Route] = [defaultRoute]
|
var routes: [NEIPv6Route] = [defaultRoute]
|
||||||
for r in ipv6.routes {
|
for r in ipv6.routes {
|
||||||
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
|
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
|
||||||
ipv6Route.gatewayAddress = r.gateway ?? ipv6.defaultGateway
|
ipv6Route.gatewayAddress = r.gateway
|
||||||
routes.append(ipv6Route)
|
routes.append(ipv6Route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,7 +542,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
|
||||||
|
|
||||||
let dnsServers = cfg.sessionConfiguration.dnsServers ?? reply.options.dnsServers
|
let dnsServers = cfg.sessionConfiguration.dnsServers ?? reply.options.dnsServers
|
||||||
let searchDomain = cfg.sessionConfiguration.searchDomain ?? reply.options.searchDomain
|
let searchDomain = cfg.sessionConfiguration.searchDomain ?? reply.options.searchDomain
|
||||||
let dnsSettings = NEDNSSettings(servers: dnsServers)
|
let dnsSettings = NEDNSSettings(servers: dnsServers ?? [])
|
||||||
dnsSettings.domainName = searchDomain
|
dnsSettings.domainName = searchDomain
|
||||||
if let searchDomain = searchDomain {
|
if let searchDomain = searchDomain {
|
||||||
dnsSettings.searchDomains = [searchDomain]
|
dnsSettings.searchDomains = [searchDomain]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// OptionsError.swift
|
// ConfigurationError.swift
|
||||||
// TunnelKit
|
// TunnelKit
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 4/3/19.
|
// Created by Davide De Rosa on 4/3/19.
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Error raised by the options parser, with details about the line that triggered it.
|
/// Error raised by the configuration parser, with details about the line that triggered it.
|
||||||
public enum OptionsError: Error {
|
public enum ConfigurationError: Error {
|
||||||
|
|
||||||
/// Option syntax is incorrect.
|
/// Option syntax is incorrect.
|
||||||
case malformed(option: String)
|
case malformed(option: String)
|
|
@ -32,26 +32,103 @@ private let log = SwiftyBeaver.self
|
||||||
/// Provides methods to parse a `SessionProxy.Configuration` from an .ovpn configuration file.
|
/// Provides methods to parse a `SessionProxy.Configuration` from an .ovpn configuration file.
|
||||||
public class ConfigurationParser {
|
public class ConfigurationParser {
|
||||||
|
|
||||||
|
// XXX: parsing is very optimistic
|
||||||
|
|
||||||
|
struct Regex {
|
||||||
|
|
||||||
|
// MARK: General
|
||||||
|
|
||||||
|
static let cipher = NSRegularExpression("^cipher +[^,\\s]+")
|
||||||
|
|
||||||
|
static let auth = NSRegularExpression("^auth +[\\w\\-]+")
|
||||||
|
|
||||||
|
static let compLZO = NSRegularExpression("^comp-lzo.*")
|
||||||
|
|
||||||
|
static let compress = NSRegularExpression("^compress.*")
|
||||||
|
|
||||||
|
static let keyDirection = NSRegularExpression("^key-direction +\\d")
|
||||||
|
|
||||||
|
static let ping = NSRegularExpression("^ping +\\d+")
|
||||||
|
|
||||||
|
static let renegSec = NSRegularExpression("^reneg-sec +\\d+")
|
||||||
|
|
||||||
|
static let blockBegin = NSRegularExpression("^<[\\w\\-]+>")
|
||||||
|
|
||||||
|
static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>")
|
||||||
|
|
||||||
|
// MARK: Client
|
||||||
|
|
||||||
|
static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)")
|
||||||
|
|
||||||
|
static let port = NSRegularExpression("^port +\\d+")
|
||||||
|
|
||||||
|
static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?")
|
||||||
|
|
||||||
|
static let eku = NSRegularExpression("^remote-cert-tls +server")
|
||||||
|
|
||||||
|
static let remoteRandom = NSRegularExpression("^remote-random")
|
||||||
|
|
||||||
|
// MARK: Server
|
||||||
|
|
||||||
|
static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+")
|
||||||
|
|
||||||
|
static let peerId = NSRegularExpression("^peer-id +[0-9]+")
|
||||||
|
|
||||||
|
// MARK: Routing
|
||||||
|
|
||||||
|
static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)")
|
||||||
|
|
||||||
|
static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+")
|
||||||
|
|
||||||
|
static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
|
||||||
|
|
||||||
|
static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}")
|
||||||
|
|
||||||
|
static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}")
|
||||||
|
|
||||||
|
static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+")
|
||||||
|
|
||||||
|
static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+")
|
||||||
|
|
||||||
|
static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+")
|
||||||
|
|
||||||
|
// MARK: Unsupported
|
||||||
|
|
||||||
|
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
||||||
|
static let fragment = NSRegularExpression("^fragment")
|
||||||
|
|
||||||
|
static let proxy = NSRegularExpression("^\\w+-proxy")
|
||||||
|
|
||||||
|
static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ")
|
||||||
|
|
||||||
|
static let connection = NSRegularExpression("^<connection>")
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Topology: String {
|
||||||
|
case net30
|
||||||
|
|
||||||
|
case p2p
|
||||||
|
|
||||||
|
case subnet
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of the parser.
|
/// Result of the parser.
|
||||||
public struct ParsingResult {
|
public struct Result {
|
||||||
|
|
||||||
/// Original URL of the configuration file, if parsed from an URL.
|
/// Original URL of the configuration file, if parsed from an URL.
|
||||||
public let url: URL?
|
public let url: URL?
|
||||||
|
|
||||||
/// The main endpoint hostname.
|
|
||||||
public let hostname: String
|
|
||||||
|
|
||||||
/// The list of `EndpointProtocol` to which the client can connect to.
|
|
||||||
public let protocols: [EndpointProtocol]
|
|
||||||
|
|
||||||
/// The overall parsed `SessionProxy.Configuration`.
|
/// The overall parsed `SessionProxy.Configuration`.
|
||||||
public let configuration: SessionProxy.Configuration
|
public let configuration: SessionProxy.Configuration
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.init(...)`
|
/// The lines of the configuration file stripped of any sensitive data. Lines that
|
||||||
|
/// the parser does not recognize are discarded in the first place.
|
||||||
|
///
|
||||||
|
/// - Seealso: `ConfigurationParser.parsed(...)`
|
||||||
public let strippedLines: [String]?
|
public let strippedLines: [String]?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.warning`
|
/// Holds an optional `ConfigurationError` that didn't block the parser, but it would be worth taking care of.
|
||||||
public let warning: OptionsError?
|
public let warning: ConfigurationError?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,77 +136,527 @@ public class ConfigurationParser {
|
||||||
|
|
||||||
- Parameter url: The URL of the configuration file.
|
- Parameter url: The URL of the configuration file.
|
||||||
- Parameter passphrase: The optional passphrase for encrypted data.
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
||||||
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
- Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`.
|
||||||
- Returns: The `ParsingResult` outcome of the parsing.
|
- Returns: The `Result` outcome of the parsing.
|
||||||
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
|
- Throws: `ConfigurationError` if the configuration file is wrong or incomplete.
|
||||||
*/
|
*/
|
||||||
public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> Result {
|
||||||
let lines = try String(contentsOf: url).trimmedLines()
|
let lines = try String(contentsOf: url).trimmedLines()
|
||||||
return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped)
|
return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parses an .ovpn file as an array of lines.
|
Parses a configuration from an array of lines.
|
||||||
|
|
||||||
- Parameter lines: The array of lines holding the configuration.
|
- Parameter lines: The array of lines holding the configuration.
|
||||||
- Parameter passphrase: The optional passphrase for encrypted data.
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
||||||
- Parameter originalURL: The optional original URL of the configuration file.
|
- Parameter originalURL: The optional original URL of the configuration file.
|
||||||
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
- Parameter returnsStripped: When `true`, stores the stripped file into `Result.strippedLines`. Defaults to `false`.
|
||||||
- Returns: The `ParsingResult` outcome of the parsing.
|
- Returns: The `Result` outcome of the parsing.
|
||||||
- Throws: `OptionsError` if the configuration file is wrong or incomplete.
|
- Throws: `ConfigurationError` if the configuration file is wrong or incomplete.
|
||||||
*/
|
*/
|
||||||
public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> Result {
|
||||||
let options = try OptionsBundle(from: lines, returnsStripped: returnsStripped)
|
var optStrippedLines: [String]? = returnsStripped ? [] : nil
|
||||||
|
var optWarning: ConfigurationError?
|
||||||
guard let ca = options.ca else {
|
var unsupportedError: ConfigurationError?
|
||||||
throw OptionsError.missingConfiguration(option: "ca")
|
var currentBlockName: String?
|
||||||
}
|
var currentBlock: [String] = []
|
||||||
guard let hostname = options.hostname, !options.remotes.isEmpty else {
|
|
||||||
throw OptionsError.missingConfiguration(option: "remote")
|
|
||||||
}
|
|
||||||
let endpointProtocols = options.remotes.map { EndpointProtocol($0.2, $0.1) }
|
|
||||||
|
|
||||||
|
var optCipher: SessionProxy.Cipher?
|
||||||
|
var optDigest: SessionProxy.Digest?
|
||||||
|
var optCompressionFraming: SessionProxy.CompressionFraming?
|
||||||
|
var optCompressionAlgorithm: SessionProxy.CompressionAlgorithm?
|
||||||
|
var optCA: CryptoContainer?
|
||||||
|
var optClientCertificate: CryptoContainer?
|
||||||
var optClientKey: CryptoContainer?
|
var optClientKey: CryptoContainer?
|
||||||
if let clientKey = options.clientKey, clientKey.isEncrypted {
|
var optKeyDirection: StaticKey.Direction?
|
||||||
|
var optTLSKeyLines: [Substring]?
|
||||||
|
var optTLSStrategy: SessionProxy.TLSWrap.Strategy?
|
||||||
|
var optKeepAliveSeconds: TimeInterval?
|
||||||
|
var optRenegotiateAfterSeconds: TimeInterval?
|
||||||
|
//
|
||||||
|
var optHostname: String?
|
||||||
|
var optDefaultProto: SocketType?
|
||||||
|
var optDefaultPort: UInt16?
|
||||||
|
var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket
|
||||||
|
var optChecksEKU: Bool?
|
||||||
|
var optRandomizeEndpoint: Bool?
|
||||||
|
//
|
||||||
|
var optAuthToken: String?
|
||||||
|
var optPeerId: UInt32?
|
||||||
|
//
|
||||||
|
var optTopology: String?
|
||||||
|
var optIfconfig4Arguments: [String]?
|
||||||
|
var optIfconfig6Arguments: [String]?
|
||||||
|
var optGateway4Arguments: [String]?
|
||||||
|
var optRoutes4: [(String, String, String?)] = [] // address, netmask, gateway
|
||||||
|
var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway
|
||||||
|
var optDNSServers: [String] = []
|
||||||
|
var optSearchDomain: String?
|
||||||
|
|
||||||
|
log.verbose("Configuration file:")
|
||||||
|
for line in lines {
|
||||||
|
log.verbose(line)
|
||||||
|
|
||||||
|
var isHandled = false
|
||||||
|
var strippedLine = line
|
||||||
|
defer {
|
||||||
|
if isHandled {
|
||||||
|
optStrippedLines?.append(strippedLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Unsupported
|
||||||
|
|
||||||
|
// check blocks first
|
||||||
|
Regex.connection.enumerateComponents(in: line) { (_) in
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks")
|
||||||
|
}
|
||||||
|
Regex.fragment.enumerateComponents(in: line) { (_) in
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment")
|
||||||
|
}
|
||||||
|
Regex.proxy.enumerateComponents(in: line) { (_) in
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
|
||||||
|
}
|
||||||
|
Regex.externalFiles.enumerateComponents(in: line) { (_) in
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "external file: \"\(line)\"")
|
||||||
|
}
|
||||||
|
if line.contains("mtu") || line.contains("mssfix") {
|
||||||
|
isHandled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Inline content
|
||||||
|
|
||||||
|
if unsupportedError == nil {
|
||||||
|
if currentBlockName == nil {
|
||||||
|
Regex.blockBegin.enumerateComponents(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
let tag = $0.first!
|
||||||
|
let from = tag.index(after: tag.startIndex)
|
||||||
|
let to = tag.index(before: tag.endIndex)
|
||||||
|
|
||||||
|
currentBlockName = String(tag[from..<to])
|
||||||
|
currentBlock = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.blockEnd.enumerateComponents(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
let tag = $0.first!
|
||||||
|
let from = tag.index(tag.startIndex, offsetBy: 2)
|
||||||
|
let to = tag.index(before: tag.endIndex)
|
||||||
|
|
||||||
|
let blockName = String(tag[from..<to])
|
||||||
|
guard blockName == currentBlockName else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// first is opening tag
|
||||||
|
currentBlock.removeFirst()
|
||||||
|
switch blockName {
|
||||||
|
case "ca":
|
||||||
|
optCA = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||||
|
|
||||||
|
case "cert":
|
||||||
|
optClientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||||
|
|
||||||
|
case "key":
|
||||||
|
ConfigurationParser.normalizeEncryptedPEMBlock(block: ¤tBlock)
|
||||||
|
optClientKey = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||||
|
|
||||||
|
case "tls-auth":
|
||||||
|
optTLSKeyLines = currentBlock.map { Substring($0) }
|
||||||
|
optTLSStrategy = .auth
|
||||||
|
|
||||||
|
case "tls-crypt":
|
||||||
|
optTLSKeyLines = currentBlock.map { Substring($0) }
|
||||||
|
optTLSStrategy = .crypt
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentBlockName = nil
|
||||||
|
currentBlock = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let _ = currentBlockName {
|
||||||
|
currentBlock.append(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: General
|
||||||
|
|
||||||
|
Regex.cipher.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let rawValue = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optCipher = SessionProxy.Cipher(rawValue: rawValue.uppercased())
|
||||||
|
if optCipher == nil {
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "cipher \(rawValue)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.auth.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let rawValue = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optDigest = SessionProxy.Digest(rawValue: rawValue.uppercased())
|
||||||
|
if optDigest == nil {
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "auth \(rawValue)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.compLZO.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
optCompressionFraming = .compLZO
|
||||||
|
|
||||||
|
if !LZOIsSupported() {
|
||||||
|
guard let arg = $0.first else {
|
||||||
|
optWarning = optWarning ?? .unsupportedConfiguration(option: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard arg == "no" else {
|
||||||
|
unsupportedError = .unsupportedConfiguration(option: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let arg = $0.first
|
||||||
|
optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.compress.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
optCompressionFraming = .compress
|
||||||
|
|
||||||
|
if !LZOIsSupported() {
|
||||||
|
guard $0.isEmpty else {
|
||||||
|
unsupportedError = .unsupportedConfiguration(option: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let arg = $0.first {
|
||||||
|
optCompressionAlgorithm = (arg == "lzo") ? .LZO : .other
|
||||||
|
} else {
|
||||||
|
optCompressionAlgorithm = .disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.keyDirection.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let arg = $0.first, let value = Int(arg) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optKeyDirection = StaticKey.Direction(rawValue: value)
|
||||||
|
}
|
||||||
|
Regex.ping.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let arg = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optKeepAliveSeconds = TimeInterval(arg)
|
||||||
|
}
|
||||||
|
Regex.renegSec.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let arg = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optRenegotiateAfterSeconds = TimeInterval(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Client
|
||||||
|
|
||||||
|
Regex.proto.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let str = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optDefaultProto = SocketType(protoString: str)
|
||||||
|
if optDefaultProto == nil {
|
||||||
|
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proto \(str)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Regex.port.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let str = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optDefaultPort = UInt16(str)
|
||||||
|
}
|
||||||
|
Regex.remote.enumerateArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard let hostname = $0.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var port: UInt16?
|
||||||
|
var proto: SocketType?
|
||||||
|
var strippedComponents = ["remote", "<hostname>"]
|
||||||
|
if $0.count > 1 {
|
||||||
|
port = UInt16($0[1])
|
||||||
|
strippedComponents.append($0[1])
|
||||||
|
}
|
||||||
|
if $0.count > 2 {
|
||||||
|
proto = SocketType(protoString: $0[2])
|
||||||
|
strippedComponents.append($0[2])
|
||||||
|
}
|
||||||
|
optRemotes.append((hostname, port, proto))
|
||||||
|
|
||||||
|
// replace private data
|
||||||
|
strippedLine = strippedComponents.joined(separator: " ")
|
||||||
|
}
|
||||||
|
Regex.eku.enumerateComponents(in: line) { (_) in
|
||||||
|
isHandled = true
|
||||||
|
optChecksEKU = true
|
||||||
|
}
|
||||||
|
Regex.remoteRandom.enumerateComponents(in: line) { (_) in
|
||||||
|
isHandled = true
|
||||||
|
optRandomizeEndpoint = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Server
|
||||||
|
|
||||||
|
Regex.authToken.enumerateArguments(in: line) {
|
||||||
|
optAuthToken = $0[0]
|
||||||
|
}
|
||||||
|
Regex.peerId.enumerateArguments(in: line) {
|
||||||
|
optPeerId = UInt32($0[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Routing
|
||||||
|
|
||||||
|
Regex.topology.enumerateArguments(in: line) {
|
||||||
|
optTopology = $0.first
|
||||||
|
}
|
||||||
|
Regex.ifconfig.enumerateArguments(in: line) {
|
||||||
|
optIfconfig4Arguments = $0
|
||||||
|
}
|
||||||
|
Regex.ifconfig6.enumerateArguments(in: line) {
|
||||||
|
optIfconfig6Arguments = $0
|
||||||
|
}
|
||||||
|
Regex.route.enumerateArguments(in: line) {
|
||||||
|
let routeEntryArguments = $0
|
||||||
|
|
||||||
|
let address = routeEntryArguments[0]
|
||||||
|
let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255"
|
||||||
|
let gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4
|
||||||
|
optRoutes4.append((address, mask, gateway))
|
||||||
|
}
|
||||||
|
Regex.route6.enumerateArguments(in: line) {
|
||||||
|
let routeEntryArguments = $0
|
||||||
|
|
||||||
|
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
|
||||||
|
guard destinationComponents.count == 2 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let prefix = UInt8(destinationComponents[1]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let destination = destinationComponents[0]
|
||||||
|
let gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6
|
||||||
|
optRoutes6.append((destination, prefix, gateway))
|
||||||
|
}
|
||||||
|
Regex.gateway.enumerateArguments(in: line) {
|
||||||
|
optGateway4Arguments = $0
|
||||||
|
}
|
||||||
|
Regex.dns.enumerateArguments(in: line) {
|
||||||
|
guard $0.count == 2 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optDNSServers.append($0[1])
|
||||||
|
}
|
||||||
|
Regex.domain.enumerateArguments(in: line) {
|
||||||
|
guard $0.count == 2 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
optSearchDomain = $0[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
if let error = unsupportedError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
var sessionBuilder = SessionProxy.ConfigurationBuilder()
|
||||||
|
|
||||||
|
// MARK: General
|
||||||
|
|
||||||
|
sessionBuilder.cipher = optCipher
|
||||||
|
sessionBuilder.digest = optDigest
|
||||||
|
sessionBuilder.compressionFraming = optCompressionFraming
|
||||||
|
sessionBuilder.compressionAlgorithm = optCompressionAlgorithm
|
||||||
|
sessionBuilder.ca = optCA
|
||||||
|
sessionBuilder.clientCertificate = optClientCertificate
|
||||||
|
|
||||||
|
if let clientKey = optClientKey, clientKey.isEncrypted {
|
||||||
guard let passphrase = passphrase else {
|
guard let passphrase = passphrase else {
|
||||||
throw OptionsError.encryptionPassphrase
|
throw ConfigurationError.encryptionPassphrase
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
optClientKey = try clientKey.decrypted(with: passphrase)
|
sessionBuilder.clientKey = try clientKey.decrypted(with: passphrase)
|
||||||
} catch let e {
|
} catch let e {
|
||||||
throw OptionsError.unableToDecrypt(error: e)
|
throw ConfigurationError.unableToDecrypt(error: e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
optClientKey = options.clientKey
|
sessionBuilder.clientKey = optClientKey
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: ca)
|
if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy {
|
||||||
sessionBuilder.cipher = options.cipher ?? .aes128cbc
|
let optKey: StaticKey?
|
||||||
sessionBuilder.digest = options.digest ?? .sha1
|
switch strategy {
|
||||||
sessionBuilder.compressionFraming = options.compressionFraming ?? .disabled
|
case .auth:
|
||||||
sessionBuilder.compressionAlgorithm = options.compressionAlgorithm ?? .disabled
|
optKey = StaticKey(lines: keyLines, direction: optKeyDirection)
|
||||||
sessionBuilder.tlsWrap = options.tlsWrap
|
|
||||||
sessionBuilder.clientCertificate = options.clientCertificate
|
case .crypt:
|
||||||
sessionBuilder.clientKey = optClientKey
|
optKey = StaticKey(lines: keyLines, direction: .client)
|
||||||
sessionBuilder.checksEKU = options.checksEKU
|
}
|
||||||
sessionBuilder.keepAliveInterval = options.keepAliveSeconds
|
if let key = optKey {
|
||||||
sessionBuilder.renegotiatesAfter = options.renegotiateAfterSeconds
|
sessionBuilder.tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key)
|
||||||
sessionBuilder.dnsServers = options.dnsServers
|
}
|
||||||
sessionBuilder.searchDomain = options.searchDomain
|
}
|
||||||
sessionBuilder.randomizeEndpoint = options.randomizeEndpoint
|
|
||||||
|
sessionBuilder.keepAliveInterval = optKeepAliveSeconds
|
||||||
|
sessionBuilder.renegotiatesAfter = optRenegotiateAfterSeconds
|
||||||
|
|
||||||
|
// MARK: Client
|
||||||
|
|
||||||
|
optDefaultProto = optDefaultProto ?? .udp
|
||||||
|
optDefaultPort = optDefaultPort ?? 1194
|
||||||
|
if !optRemotes.isEmpty {
|
||||||
|
sessionBuilder.hostname = optRemotes[0].0
|
||||||
|
|
||||||
|
var fullRemotes: [(String, UInt16, SocketType)] = []
|
||||||
|
let hostname = optRemotes[0].0
|
||||||
|
optRemotes.forEach {
|
||||||
|
guard $0.0 == hostname else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let port = $0.1 ?? optDefaultPort else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let socketType = $0.2 ?? optDefaultProto else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fullRemotes.append((hostname, port, socketType))
|
||||||
|
}
|
||||||
|
sessionBuilder.endpointProtocols = fullRemotes.map { EndpointProtocol($0.2, $0.1) }
|
||||||
|
} else {
|
||||||
|
sessionBuilder.hostname = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionBuilder.checksEKU = optChecksEKU
|
||||||
|
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
||||||
|
|
||||||
|
// MARK: Server
|
||||||
|
|
||||||
|
sessionBuilder.authToken = optAuthToken
|
||||||
|
sessionBuilder.peerId = optPeerId
|
||||||
|
|
||||||
|
// MARK: Routing
|
||||||
|
|
||||||
|
//
|
||||||
|
// excerpts from OpenVPN manpage
|
||||||
|
//
|
||||||
|
// "--ifconfig l rn":
|
||||||
|
//
|
||||||
|
// Set TUN/TAP adapter parameters. l is the IP address of the local VPN endpoint. For TUN devices in point-to-point mode, rn is the IP address of
|
||||||
|
// the remote VPN endpoint. For TAP devices, or TUN devices used with --topology subnet, rn is the subnet mask of the virtual network segment which
|
||||||
|
// is being created or connected to.
|
||||||
|
//
|
||||||
|
// "--topology mode":
|
||||||
|
//
|
||||||
|
// Note: Using --topology subnet changes the interpretation of the arguments of --ifconfig to mean "address netmask", no longer "local remote".
|
||||||
|
//
|
||||||
|
if let ifconfig4Arguments = optIfconfig4Arguments {
|
||||||
|
guard ifconfig4Arguments.count == 2 else {
|
||||||
|
throw ConfigurationError.malformed(option: "ifconfig takes 2 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
let address4: String
|
||||||
|
let addressMask4: String
|
||||||
|
let defaultGateway4: String
|
||||||
|
|
||||||
|
let topology = Topology(rawValue: optTopology ?? "") ?? .net30
|
||||||
|
switch topology {
|
||||||
|
case .subnet:
|
||||||
|
|
||||||
|
// default gateway required when topology is subnet
|
||||||
|
guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else {
|
||||||
|
throw ConfigurationError.malformed(option: "route-gateway takes 1 argument")
|
||||||
|
}
|
||||||
|
address4 = ifconfig4Arguments[0]
|
||||||
|
addressMask4 = ifconfig4Arguments[1]
|
||||||
|
defaultGateway4 = gateway4Arguments[0]
|
||||||
|
|
||||||
|
default:
|
||||||
|
address4 = ifconfig4Arguments[0]
|
||||||
|
addressMask4 = "255.255.255.255"
|
||||||
|
defaultGateway4 = ifconfig4Arguments[1]
|
||||||
|
}
|
||||||
|
let routes4 = optRoutes4.map { IPv4Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway4) }
|
||||||
|
|
||||||
return ParsingResult(
|
sessionBuilder.ipv4 = IPv4Settings(
|
||||||
|
address: address4,
|
||||||
|
addressMask: addressMask4,
|
||||||
|
defaultGateway: defaultGateway4,
|
||||||
|
routes: routes4
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ifconfig6Arguments = optIfconfig6Arguments {
|
||||||
|
guard ifconfig6Arguments.count == 2 else {
|
||||||
|
throw ConfigurationError.malformed(option: "ifconfig-ipv6 takes 2 arguments")
|
||||||
|
}
|
||||||
|
let address6Components = ifconfig6Arguments[0].components(separatedBy: "/")
|
||||||
|
guard address6Components.count == 2 else {
|
||||||
|
throw ConfigurationError.malformed(option: "ifconfig-ipv6 address must have a /prefix")
|
||||||
|
}
|
||||||
|
guard let addressPrefix6 = UInt8(address6Components[1]) else {
|
||||||
|
throw ConfigurationError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number")
|
||||||
|
}
|
||||||
|
|
||||||
|
let address6 = address6Components[0]
|
||||||
|
let defaultGateway6 = ifconfig6Arguments[1]
|
||||||
|
let routes6 = optRoutes6.map { IPv6Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway6) }
|
||||||
|
|
||||||
|
sessionBuilder.ipv6 = IPv6Settings(
|
||||||
|
address: address6,
|
||||||
|
addressPrefixLength: addressPrefix6,
|
||||||
|
defaultGateway: defaultGateway6,
|
||||||
|
routes: routes6
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionBuilder.dnsServers = optDNSServers
|
||||||
|
sessionBuilder.searchDomain = optSearchDomain
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
return Result(
|
||||||
url: originalURL,
|
url: originalURL,
|
||||||
hostname: hostname,
|
|
||||||
protocols: endpointProtocols,
|
|
||||||
configuration: sessionBuilder.build(),
|
configuration: sessionBuilder.build(),
|
||||||
strippedLines: options.strippedLines,
|
strippedLines: optStrippedLines,
|
||||||
warning: options.warning
|
warning: optWarning
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func normalizeEncryptedPEMBlock(block: inout [String]) {
|
||||||
|
// if block.count >= 1 && block[0].contains("ENCRYPTED") {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// XXX: restore blank line after encryption header (easier than tweaking trimmedLines)
|
||||||
|
if block.count >= 3 && block[1].contains("Proc-Type") {
|
||||||
|
block.insert("", at: 3)
|
||||||
|
// return true
|
||||||
|
}
|
||||||
|
// return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
private extension String {
|
||||||
func trimmedLines() -> [String] {
|
func trimmedLines() -> [String] {
|
||||||
return components(separatedBy: .newlines).map {
|
return components(separatedBy: .newlines).map {
|
||||||
$0.trimmingCharacters(in: .whitespacesAndNewlines)
|
$0.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
@ -138,3 +665,13 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension SocketType {
|
||||||
|
init?(protoString: String) {
|
||||||
|
var str = protoString
|
||||||
|
if str.hasSuffix("6") {
|
||||||
|
str.removeLast()
|
||||||
|
}
|
||||||
|
self.init(rawValue: str.uppercased())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,792 +0,0 @@
|
||||||
//
|
|
||||||
// OptionsBundle.swift
|
|
||||||
// TunnelKit
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 4/3/19.
|
|
||||||
// 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/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyBeaver
|
|
||||||
import __TunnelKitNative
|
|
||||||
|
|
||||||
private let log = SwiftyBeaver.self
|
|
||||||
|
|
||||||
/// Wraps together all recognized options from either configuration files or PUSH_REPLY.
|
|
||||||
public struct OptionsBundle {
|
|
||||||
struct Regex {
|
|
||||||
|
|
||||||
// MARK: General
|
|
||||||
|
|
||||||
static let cipher = NSRegularExpression("^cipher +[^,\\s]+")
|
|
||||||
|
|
||||||
static let auth = NSRegularExpression("^auth +[\\w\\-]+")
|
|
||||||
|
|
||||||
static let compLZO = NSRegularExpression("^comp-lzo.*")
|
|
||||||
|
|
||||||
static let compress = NSRegularExpression("^compress.*")
|
|
||||||
|
|
||||||
static let keyDirection = NSRegularExpression("^key-direction +\\d")
|
|
||||||
|
|
||||||
static let ping = NSRegularExpression("^ping +\\d+")
|
|
||||||
|
|
||||||
static let renegSec = NSRegularExpression("^reneg-sec +\\d+")
|
|
||||||
|
|
||||||
static let blockBegin = NSRegularExpression("^<[\\w\\-]+>")
|
|
||||||
|
|
||||||
static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>")
|
|
||||||
|
|
||||||
// MARK: Client
|
|
||||||
|
|
||||||
static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)")
|
|
||||||
|
|
||||||
static let port = NSRegularExpression("^port +\\d+")
|
|
||||||
|
|
||||||
static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?")
|
|
||||||
|
|
||||||
static let eku = NSRegularExpression("^remote-cert-tls +server")
|
|
||||||
|
|
||||||
static let remoteRandom = NSRegularExpression("^remote-random")
|
|
||||||
|
|
||||||
// MARK: Server
|
|
||||||
|
|
||||||
static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+")
|
|
||||||
|
|
||||||
static let peerId = NSRegularExpression("^peer-id +[0-9]+")
|
|
||||||
|
|
||||||
// MARK: Routing
|
|
||||||
|
|
||||||
static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)")
|
|
||||||
|
|
||||||
static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+")
|
|
||||||
|
|
||||||
static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
|
|
||||||
|
|
||||||
static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}")
|
|
||||||
|
|
||||||
static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}")
|
|
||||||
|
|
||||||
static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+")
|
|
||||||
|
|
||||||
static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+")
|
|
||||||
|
|
||||||
static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+")
|
|
||||||
|
|
||||||
// MARK: Unsupported
|
|
||||||
|
|
||||||
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
|
||||||
static let fragment = NSRegularExpression("^fragment")
|
|
||||||
|
|
||||||
static let proxy = NSRegularExpression("^\\w+-proxy")
|
|
||||||
|
|
||||||
static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ")
|
|
||||||
|
|
||||||
static let connection = NSRegularExpression("^<connection>")
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Topology: String {
|
|
||||||
case net30
|
|
||||||
|
|
||||||
case p2p
|
|
||||||
|
|
||||||
case subnet
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The lines of the configuration file stripped of any sensitive data. Lines that
|
|
||||||
/// the parser does not recognize are discarded in the first place.
|
|
||||||
///
|
|
||||||
/// - Seealso: `OptionsBundle.init(...)`
|
|
||||||
public let strippedLines: [String]?
|
|
||||||
|
|
||||||
/// Holds an optional `OptionsError` that didn't block the parser, but it would be worth taking care of.
|
|
||||||
public let warning: OptionsError?
|
|
||||||
|
|
||||||
// MARK: General
|
|
||||||
|
|
||||||
/// The cipher algorithm for data encryption.
|
|
||||||
public let cipher: SessionProxy.Cipher?
|
|
||||||
|
|
||||||
/// The digest algorithm for HMAC.
|
|
||||||
public let digest: SessionProxy.Digest?
|
|
||||||
|
|
||||||
/// Compression framing, disabled by default.
|
|
||||||
public let compressionFraming: SessionProxy.CompressionFraming?
|
|
||||||
|
|
||||||
/// Compression algorithm, disabled by default.
|
|
||||||
public let compressionAlgorithm: SessionProxy.CompressionAlgorithm?
|
|
||||||
|
|
||||||
/// The CA for TLS negotiation (PEM format).
|
|
||||||
public let ca: CryptoContainer?
|
|
||||||
|
|
||||||
/// The optional client certificate for TLS negotiation (PEM format).
|
|
||||||
public let clientCertificate: CryptoContainer?
|
|
||||||
|
|
||||||
/// The private key for the certificate in `clientCertificate` (PEM format).
|
|
||||||
public let clientKey: CryptoContainer?
|
|
||||||
|
|
||||||
/// The optional TLS wrapping.
|
|
||||||
public let tlsWrap: SessionProxy.TLSWrap?
|
|
||||||
|
|
||||||
/// Sends periodical keep-alive packets if set.
|
|
||||||
public let keepAliveSeconds: TimeInterval?
|
|
||||||
|
|
||||||
/// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation.
|
|
||||||
public let renegotiateAfterSeconds: TimeInterval?
|
|
||||||
|
|
||||||
// MARK: Client
|
|
||||||
|
|
||||||
/// The server hostname (picked from first remote).
|
|
||||||
public let hostname: String?
|
|
||||||
|
|
||||||
/// The list of server endpoints (address, port, socket).
|
|
||||||
public let remotes: [(String, UInt16, SocketType)]
|
|
||||||
|
|
||||||
/// If true, checks EKU of server certificate.
|
|
||||||
public let checksEKU: Bool
|
|
||||||
|
|
||||||
/// Picks endpoint from `remotes` randomly.
|
|
||||||
public let randomizeEndpoint: Bool
|
|
||||||
|
|
||||||
// MARK: Server
|
|
||||||
|
|
||||||
/// The auth-token returned by the server.
|
|
||||||
public let authToken: String?
|
|
||||||
|
|
||||||
/// The peer-id returned by the server.
|
|
||||||
public let peerId: UInt32?
|
|
||||||
|
|
||||||
// MARK: Routing
|
|
||||||
|
|
||||||
/// The settings for IPv4.
|
|
||||||
public let ipv4: IPv4Settings?
|
|
||||||
|
|
||||||
/// The settings for IPv6.
|
|
||||||
public let ipv6: IPv6Settings?
|
|
||||||
|
|
||||||
/// The DNS servers.
|
|
||||||
public let dnsServers: [String]
|
|
||||||
|
|
||||||
/// The search domain.
|
|
||||||
public let searchDomain: String?
|
|
||||||
|
|
||||||
/**
|
|
||||||
Parses options from an array of lines.
|
|
||||||
|
|
||||||
- Parameter lines: The array of lines holding the options.
|
|
||||||
- Parameter returnsStripped: When `true`, stores the stripped lines into `strippedLines`. Defaults to `false`.
|
|
||||||
- Throws: `OptionsError` if the options are wrong or incomplete.
|
|
||||||
*/
|
|
||||||
public init(from lines: [String], returnsStripped: Bool = false) throws {
|
|
||||||
var optStrippedLines: [String]? = returnsStripped ? [] : nil
|
|
||||||
var optWarning: OptionsError?
|
|
||||||
var unsupportedError: OptionsError?
|
|
||||||
var currentBlockName: String?
|
|
||||||
var currentBlock: [String] = []
|
|
||||||
|
|
||||||
var optCipher: SessionProxy.Cipher?
|
|
||||||
var optDigest: SessionProxy.Digest?
|
|
||||||
var optCompressionFraming: SessionProxy.CompressionFraming?
|
|
||||||
var optCompressionAlgorithm: SessionProxy.CompressionAlgorithm?
|
|
||||||
var optCA: CryptoContainer?
|
|
||||||
var optClientCertificate: CryptoContainer?
|
|
||||||
var optClientKey: CryptoContainer?
|
|
||||||
var optKeyDirection: StaticKey.Direction?
|
|
||||||
var optTLSKeyLines: [Substring]?
|
|
||||||
var optTLSStrategy: SessionProxy.TLSWrap.Strategy?
|
|
||||||
var optKeepAliveSeconds: TimeInterval?
|
|
||||||
var optRenegotiateAfterSeconds: TimeInterval?
|
|
||||||
//
|
|
||||||
var optHostname: String?
|
|
||||||
var optDefaultProto: SocketType?
|
|
||||||
var optDefaultPort: UInt16?
|
|
||||||
var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket
|
|
||||||
var optChecksEKU: Bool?
|
|
||||||
var optRandomizeEndpoint: Bool?
|
|
||||||
//
|
|
||||||
var optAuthToken: String?
|
|
||||||
var optPeerId: UInt32?
|
|
||||||
//
|
|
||||||
var optTopology: String?
|
|
||||||
var optIfconfig4Arguments: [String]?
|
|
||||||
var optIfconfig6Arguments: [String]?
|
|
||||||
var optGateway4Arguments: [String]?
|
|
||||||
var optRoutes4: [(String, String, String?)] = [] // address, netmask, gateway
|
|
||||||
var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway
|
|
||||||
var optDNSServers: [String] = []
|
|
||||||
var optSearchDomain: String?
|
|
||||||
|
|
||||||
log.verbose("Configuration file:")
|
|
||||||
for line in lines {
|
|
||||||
log.verbose(line)
|
|
||||||
|
|
||||||
var isHandled = false
|
|
||||||
var strippedLine = line
|
|
||||||
defer {
|
|
||||||
if isHandled {
|
|
||||||
optStrippedLines?.append(strippedLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Unsupported
|
|
||||||
|
|
||||||
// check blocks first
|
|
||||||
Regex.connection.enumerateComponents(in: line) { (_) in
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "<connection> blocks")
|
|
||||||
}
|
|
||||||
Regex.fragment.enumerateComponents(in: line) { (_) in
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "fragment")
|
|
||||||
}
|
|
||||||
Regex.proxy.enumerateComponents(in: line) { (_) in
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
|
|
||||||
}
|
|
||||||
Regex.externalFiles.enumerateComponents(in: line) { (_) in
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "external file: \"\(line)\"")
|
|
||||||
}
|
|
||||||
if line.contains("mtu") || line.contains("mssfix") {
|
|
||||||
isHandled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Inline content
|
|
||||||
|
|
||||||
if unsupportedError == nil {
|
|
||||||
if currentBlockName == nil {
|
|
||||||
Regex.blockBegin.enumerateComponents(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
let tag = $0.first!
|
|
||||||
let from = tag.index(after: tag.startIndex)
|
|
||||||
let to = tag.index(before: tag.endIndex)
|
|
||||||
|
|
||||||
currentBlockName = String(tag[from..<to])
|
|
||||||
currentBlock = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.blockEnd.enumerateComponents(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
let tag = $0.first!
|
|
||||||
let from = tag.index(tag.startIndex, offsetBy: 2)
|
|
||||||
let to = tag.index(before: tag.endIndex)
|
|
||||||
|
|
||||||
let blockName = String(tag[from..<to])
|
|
||||||
guard blockName == currentBlockName else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// first is opening tag
|
|
||||||
currentBlock.removeFirst()
|
|
||||||
switch blockName {
|
|
||||||
case "ca":
|
|
||||||
optCA = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
|
||||||
|
|
||||||
case "cert":
|
|
||||||
optClientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
|
||||||
|
|
||||||
case "key":
|
|
||||||
OptionsBundle.normalizeEncryptedPEMBlock(block: ¤tBlock)
|
|
||||||
optClientKey = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
|
||||||
|
|
||||||
case "tls-auth":
|
|
||||||
optTLSKeyLines = currentBlock.map { Substring($0) }
|
|
||||||
optTLSStrategy = .auth
|
|
||||||
|
|
||||||
case "tls-crypt":
|
|
||||||
optTLSKeyLines = currentBlock.map { Substring($0) }
|
|
||||||
optTLSStrategy = .crypt
|
|
||||||
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
currentBlockName = nil
|
|
||||||
currentBlock = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let _ = currentBlockName {
|
|
||||||
currentBlock.append(line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: General
|
|
||||||
|
|
||||||
Regex.cipher.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let rawValue = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optCipher = SessionProxy.Cipher(rawValue: rawValue.uppercased())
|
|
||||||
if optCipher == nil {
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "cipher \(rawValue)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.auth.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let rawValue = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optDigest = SessionProxy.Digest(rawValue: rawValue.uppercased())
|
|
||||||
if optDigest == nil {
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "auth \(rawValue)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.compLZO.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
optCompressionFraming = .compLZO
|
|
||||||
|
|
||||||
if !LZOIsSupported() {
|
|
||||||
guard let arg = $0.first else {
|
|
||||||
optWarning = optWarning ?? .unsupportedConfiguration(option: line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard arg == "no" else {
|
|
||||||
unsupportedError = .unsupportedConfiguration(option: line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let arg = $0.first
|
|
||||||
optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.compress.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
optCompressionFraming = .compress
|
|
||||||
|
|
||||||
if !LZOIsSupported() {
|
|
||||||
guard $0.isEmpty else {
|
|
||||||
unsupportedError = .unsupportedConfiguration(option: line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let arg = $0.first {
|
|
||||||
optCompressionAlgorithm = (arg == "lzo") ? .LZO : .other
|
|
||||||
} else {
|
|
||||||
optCompressionAlgorithm = .disabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.keyDirection.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let arg = $0.first, let value = Int(arg) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optKeyDirection = StaticKey.Direction(rawValue: value)
|
|
||||||
}
|
|
||||||
Regex.ping.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let arg = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optKeepAliveSeconds = TimeInterval(arg)
|
|
||||||
}
|
|
||||||
Regex.renegSec.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let arg = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optRenegotiateAfterSeconds = TimeInterval(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Client
|
|
||||||
|
|
||||||
Regex.proto.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let str = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optDefaultProto = SocketType(protoString: str)
|
|
||||||
if optDefaultProto == nil {
|
|
||||||
unsupportedError = OptionsError.unsupportedConfiguration(option: "proto \(str)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Regex.port.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let str = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optDefaultPort = UInt16(str)
|
|
||||||
}
|
|
||||||
Regex.remote.enumerateArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
guard let hostname = $0.first else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var port: UInt16?
|
|
||||||
var proto: SocketType?
|
|
||||||
var strippedComponents = ["remote", "<hostname>"]
|
|
||||||
if $0.count > 1 {
|
|
||||||
port = UInt16($0[1])
|
|
||||||
strippedComponents.append($0[1])
|
|
||||||
}
|
|
||||||
if $0.count > 2 {
|
|
||||||
proto = SocketType(protoString: $0[2])
|
|
||||||
strippedComponents.append($0[2])
|
|
||||||
}
|
|
||||||
optRemotes.append((hostname, port, proto))
|
|
||||||
|
|
||||||
// replace private data
|
|
||||||
strippedLine = strippedComponents.joined(separator: " ")
|
|
||||||
}
|
|
||||||
Regex.eku.enumerateComponents(in: line) { (_) in
|
|
||||||
isHandled = true
|
|
||||||
optChecksEKU = true
|
|
||||||
}
|
|
||||||
Regex.remoteRandom.enumerateComponents(in: line) { (_) in
|
|
||||||
isHandled = true
|
|
||||||
optRandomizeEndpoint = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Server
|
|
||||||
|
|
||||||
Regex.authToken.enumerateArguments(in: line) {
|
|
||||||
optAuthToken = $0[0]
|
|
||||||
}
|
|
||||||
Regex.peerId.enumerateArguments(in: line) {
|
|
||||||
optPeerId = UInt32($0[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Routing
|
|
||||||
|
|
||||||
Regex.topology.enumerateArguments(in: line) {
|
|
||||||
optTopology = $0.first
|
|
||||||
}
|
|
||||||
Regex.ifconfig.enumerateArguments(in: line) {
|
|
||||||
optIfconfig4Arguments = $0
|
|
||||||
}
|
|
||||||
Regex.ifconfig6.enumerateArguments(in: line) {
|
|
||||||
optIfconfig6Arguments = $0
|
|
||||||
}
|
|
||||||
Regex.route.enumerateArguments(in: line) {
|
|
||||||
let routeEntryArguments = $0
|
|
||||||
|
|
||||||
let address = routeEntryArguments[0]
|
|
||||||
let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255"
|
|
||||||
let gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4
|
|
||||||
optRoutes4.append((address, mask, gateway))
|
|
||||||
}
|
|
||||||
Regex.route6.enumerateArguments(in: line) {
|
|
||||||
let routeEntryArguments = $0
|
|
||||||
|
|
||||||
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
|
|
||||||
guard destinationComponents.count == 2 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let prefix = UInt8(destinationComponents[1]) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let destination = destinationComponents[0]
|
|
||||||
let gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6
|
|
||||||
optRoutes6.append((destination, prefix, gateway))
|
|
||||||
}
|
|
||||||
Regex.gateway.enumerateArguments(in: line) {
|
|
||||||
optGateway4Arguments = $0
|
|
||||||
}
|
|
||||||
Regex.dns.enumerateArguments(in: line) {
|
|
||||||
guard $0.count == 2 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optDNSServers.append($0[1])
|
|
||||||
}
|
|
||||||
Regex.domain.enumerateArguments(in: line) {
|
|
||||||
guard $0.count == 2 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optSearchDomain = $0[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
if let error = unsupportedError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
strippedLines = optStrippedLines
|
|
||||||
warning = optWarning
|
|
||||||
|
|
||||||
// MARK: General
|
|
||||||
|
|
||||||
cipher = optCipher
|
|
||||||
digest = optDigest
|
|
||||||
compressionFraming = optCompressionFraming
|
|
||||||
compressionAlgorithm = optCompressionAlgorithm
|
|
||||||
ca = optCA
|
|
||||||
clientCertificate = optClientCertificate
|
|
||||||
clientKey = optClientKey
|
|
||||||
|
|
||||||
if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy {
|
|
||||||
let optKey: StaticKey?
|
|
||||||
switch strategy {
|
|
||||||
case .auth:
|
|
||||||
optKey = StaticKey(lines: keyLines, direction: optKeyDirection)
|
|
||||||
|
|
||||||
case .crypt:
|
|
||||||
optKey = StaticKey(lines: keyLines, direction: .client)
|
|
||||||
}
|
|
||||||
if let key = optKey {
|
|
||||||
tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key)
|
|
||||||
} else {
|
|
||||||
tlsWrap = nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tlsWrap = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keepAliveSeconds = optKeepAliveSeconds
|
|
||||||
renegotiateAfterSeconds = optRenegotiateAfterSeconds
|
|
||||||
|
|
||||||
// MARK: Client
|
|
||||||
|
|
||||||
optDefaultProto = optDefaultProto ?? .udp
|
|
||||||
optDefaultPort = optDefaultPort ?? 1194
|
|
||||||
if !optRemotes.isEmpty {
|
|
||||||
hostname = optRemotes[0].0
|
|
||||||
|
|
||||||
var fullRemotes: [(String, UInt16, SocketType)] = []
|
|
||||||
let hostname = optRemotes[0].0
|
|
||||||
optRemotes.forEach {
|
|
||||||
guard $0.0 == hostname else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let port = $0.1 ?? optDefaultPort else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let socketType = $0.2 ?? optDefaultProto else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fullRemotes.append((hostname, port, socketType))
|
|
||||||
}
|
|
||||||
remotes = fullRemotes
|
|
||||||
} else {
|
|
||||||
hostname = nil
|
|
||||||
remotes = []
|
|
||||||
}
|
|
||||||
|
|
||||||
checksEKU = optChecksEKU ?? false
|
|
||||||
randomizeEndpoint = optRandomizeEndpoint ?? false
|
|
||||||
|
|
||||||
// MARK: Server
|
|
||||||
|
|
||||||
authToken = optAuthToken
|
|
||||||
peerId = optPeerId
|
|
||||||
|
|
||||||
// MARK: Routing
|
|
||||||
|
|
||||||
//
|
|
||||||
// excerpts from OpenVPN manpage
|
|
||||||
//
|
|
||||||
// "--ifconfig l rn":
|
|
||||||
//
|
|
||||||
// Set TUN/TAP adapter parameters. l is the IP address of the local VPN endpoint. For TUN devices in point-to-point mode, rn is the IP address of
|
|
||||||
// the remote VPN endpoint. For TAP devices, or TUN devices used with --topology subnet, rn is the subnet mask of the virtual network segment which
|
|
||||||
// is being created or connected to.
|
|
||||||
//
|
|
||||||
// "--topology mode":
|
|
||||||
//
|
|
||||||
// Note: Using --topology subnet changes the interpretation of the arguments of --ifconfig to mean "address netmask", no longer "local remote".
|
|
||||||
//
|
|
||||||
if let ifconfig4Arguments = optIfconfig4Arguments {
|
|
||||||
guard ifconfig4Arguments.count == 2 else {
|
|
||||||
throw OptionsError.malformed(option: "ifconfig takes 2 arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
let address4: String
|
|
||||||
let addressMask4: String
|
|
||||||
let defaultGateway4: String
|
|
||||||
|
|
||||||
let topology = Topology(rawValue: optTopology ?? "") ?? .net30
|
|
||||||
switch topology {
|
|
||||||
case .subnet:
|
|
||||||
|
|
||||||
// default gateway required when topology is subnet
|
|
||||||
guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else {
|
|
||||||
throw OptionsError.malformed(option: "route-gateway takes 1 argument")
|
|
||||||
}
|
|
||||||
address4 = ifconfig4Arguments[0]
|
|
||||||
addressMask4 = ifconfig4Arguments[1]
|
|
||||||
defaultGateway4 = gateway4Arguments[0]
|
|
||||||
|
|
||||||
default:
|
|
||||||
address4 = ifconfig4Arguments[0]
|
|
||||||
addressMask4 = "255.255.255.255"
|
|
||||||
defaultGateway4 = ifconfig4Arguments[1]
|
|
||||||
}
|
|
||||||
let routes4 = optRoutes4.map { IPv4Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway4) }
|
|
||||||
|
|
||||||
ipv4 = IPv4Settings(
|
|
||||||
address: address4,
|
|
||||||
addressMask: addressMask4,
|
|
||||||
defaultGateway: defaultGateway4,
|
|
||||||
routes: routes4
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ipv4 = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ifconfig6Arguments = optIfconfig6Arguments {
|
|
||||||
guard ifconfig6Arguments.count == 2 else {
|
|
||||||
throw OptionsError.malformed(option: "ifconfig-ipv6 takes 2 arguments")
|
|
||||||
}
|
|
||||||
let address6Components = ifconfig6Arguments[0].components(separatedBy: "/")
|
|
||||||
guard address6Components.count == 2 else {
|
|
||||||
throw OptionsError.malformed(option: "ifconfig-ipv6 address must have a /prefix")
|
|
||||||
}
|
|
||||||
guard let addressPrefix6 = UInt8(address6Components[1]) else {
|
|
||||||
throw OptionsError.malformed(option: "ifconfig-ipv6 address prefix must be a 8-bit number")
|
|
||||||
}
|
|
||||||
|
|
||||||
let address6 = address6Components[0]
|
|
||||||
let defaultGateway6 = ifconfig6Arguments[1]
|
|
||||||
let routes6 = optRoutes6.map { IPv6Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway6) }
|
|
||||||
|
|
||||||
ipv6 = IPv6Settings(
|
|
||||||
address: address6,
|
|
||||||
addressPrefixLength: addressPrefix6,
|
|
||||||
defaultGateway: defaultGateway6,
|
|
||||||
routes: routes6
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ipv6 = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsServers = optDNSServers
|
|
||||||
searchDomain = optSearchDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func normalizeEncryptedPEMBlock(block: inout [String]) {
|
|
||||||
// if block.count >= 1 && block[0].contains("ENCRYPTED") {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// XXX: restore blank line after encryption header (easier than tweaking trimmedLines)
|
|
||||||
if block.count >= 3 && block[1].contains("Proc-Type") {
|
|
||||||
block.insert("", at: 3)
|
|
||||||
// return true
|
|
||||||
}
|
|
||||||
// return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encapsulates the IPv4 settings for the tunnel.
|
|
||||||
public struct IPv4Settings: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// Represents an IPv4 route in the routing table.
|
|
||||||
public struct Route: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// The destination host or subnet.
|
|
||||||
public let destination: String
|
|
||||||
|
|
||||||
/// The address mask.
|
|
||||||
public let mask: String
|
|
||||||
|
|
||||||
/// The address of the gateway (uses default gateway if not set).
|
|
||||||
public let gateway: String?
|
|
||||||
|
|
||||||
fileprivate init(_ destination: String, _ mask: String?, _ gateway: String?) {
|
|
||||||
self.destination = destination
|
|
||||||
self.mask = mask ?? "255.255.255.255"
|
|
||||||
self.gateway = gateway
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: CustomStringConvertible
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public var description: String {
|
|
||||||
return "{\(destination.maskedDescription)/\(mask) \(gateway?.maskedDescription ?? "default")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The address.
|
|
||||||
let address: String
|
|
||||||
|
|
||||||
/// The address mask.
|
|
||||||
let addressMask: String
|
|
||||||
|
|
||||||
/// The address of the default gateway.
|
|
||||||
let defaultGateway: String
|
|
||||||
|
|
||||||
/// The additional routes.
|
|
||||||
let routes: [Route]
|
|
||||||
|
|
||||||
// MARK: CustomStringConvertible
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public var description: String {
|
|
||||||
return "addr \(address.maskedDescription) netmask \(addressMask) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encapsulates the IPv6 settings for the tunnel.
|
|
||||||
public struct IPv6Settings: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// Represents an IPv6 route in the routing table.
|
|
||||||
public struct Route: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// The destination host or subnet.
|
|
||||||
public let destination: String
|
|
||||||
|
|
||||||
/// The address prefix length.
|
|
||||||
public let prefixLength: UInt8
|
|
||||||
|
|
||||||
/// The address of the gateway (uses default gateway if not set).
|
|
||||||
public let gateway: String?
|
|
||||||
|
|
||||||
fileprivate init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) {
|
|
||||||
self.destination = destination
|
|
||||||
self.prefixLength = prefixLength ?? 3
|
|
||||||
self.gateway = gateway
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: CustomStringConvertible
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public var description: String {
|
|
||||||
return "{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "default")}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The address.
|
|
||||||
public let address: String
|
|
||||||
|
|
||||||
/// The address prefix length.
|
|
||||||
public let addressPrefixLength: UInt8
|
|
||||||
|
|
||||||
/// The address of the default gateway.
|
|
||||||
public let defaultGateway: String
|
|
||||||
|
|
||||||
/// The additional routes.
|
|
||||||
public let routes: [Route]
|
|
||||||
|
|
||||||
// MARK: CustomStringConvertible
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public var description: String {
|
|
||||||
return "addr \(address.maskedDescription)/\(addressPrefixLength) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension SocketType {
|
|
||||||
init?(protoString: String) {
|
|
||||||
var str = protoString
|
|
||||||
if str.hasSuffix("6") {
|
|
||||||
str.removeLast()
|
|
||||||
}
|
|
||||||
self.init(rawValue: str.uppercased())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -132,73 +132,93 @@ extension SessionProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
private struct Fallback {
|
||||||
|
static let cipher: Cipher = .aes128cbc
|
||||||
|
|
||||||
|
static let digest: Digest = .sha1
|
||||||
|
|
||||||
|
static let compressionFraming: CompressionFraming = .disabled
|
||||||
|
}
|
||||||
|
|
||||||
/// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`.
|
/// The way to create a `SessionProxy.Configuration` object for a `SessionProxy`.
|
||||||
public struct ConfigurationBuilder {
|
public struct ConfigurationBuilder {
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.cipher`
|
// MARK: General
|
||||||
public var cipher: Cipher
|
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.digest`
|
/// The cipher algorithm for data encryption.
|
||||||
public var digest: Digest
|
public var cipher: SessionProxy.Cipher?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.ca`
|
/// The digest algorithm for HMAC.
|
||||||
public let ca: CryptoContainer
|
public var digest: SessionProxy.Digest?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.clientCertificate`
|
/// Compression framing, disabled by default.
|
||||||
|
public var compressionFraming: SessionProxy.CompressionFraming?
|
||||||
|
|
||||||
|
/// Compression algorithm, disabled by default.
|
||||||
|
public var compressionAlgorithm: SessionProxy.CompressionAlgorithm?
|
||||||
|
|
||||||
|
/// The CA for TLS negotiation (PEM format).
|
||||||
|
public var ca: CryptoContainer?
|
||||||
|
|
||||||
|
/// The optional client certificate for TLS negotiation (PEM format).
|
||||||
public var clientCertificate: CryptoContainer?
|
public var clientCertificate: CryptoContainer?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.clientKey`
|
/// The private key for the certificate in `clientCertificate` (PEM format).
|
||||||
public var clientKey: CryptoContainer?
|
public var clientKey: CryptoContainer?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.checksEKU`
|
/// The optional TLS wrapping.
|
||||||
public var checksEKU: Bool?
|
public var tlsWrap: SessionProxy.TLSWrap?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.compressionFraming`
|
/// Sends periodical keep-alive packets if set.
|
||||||
public var compressionFraming: CompressionFraming
|
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.compressionAlgorithm`
|
|
||||||
public var compressionAlgorithm: CompressionAlgorithm?
|
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.tlsWrap`
|
|
||||||
public var tlsWrap: TLSWrap?
|
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.keepAliveInterval`
|
|
||||||
public var keepAliveInterval: TimeInterval?
|
public var keepAliveInterval: TimeInterval?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.renegotiatesAfter`
|
/// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation.
|
||||||
public var renegotiatesAfter: TimeInterval?
|
public var renegotiatesAfter: TimeInterval?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.dnsServers`
|
// MARK: Client
|
||||||
public var dnsServers: [String]?
|
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.searchDomain`
|
/// The server hostname (picked from first remote).
|
||||||
public var searchDomain: String?
|
public var hostname: String?
|
||||||
|
|
||||||
/// - Seealso: `OptionsBundle.randomizeEndpoint`
|
/// The list of server endpoints.
|
||||||
|
public var endpointProtocols: [EndpointProtocol]?
|
||||||
|
|
||||||
|
/// If true, checks EKU of server certificate.
|
||||||
|
public var checksEKU: Bool?
|
||||||
|
|
||||||
|
/// Picks endpoint from `remotes` randomly.
|
||||||
public var randomizeEndpoint: Bool?
|
public var randomizeEndpoint: Bool?
|
||||||
|
|
||||||
/// Server is patched for the PIA VPN provider.
|
/// Server is patched for the PIA VPN provider.
|
||||||
public var usesPIAPatches: Bool?
|
public var usesPIAPatches: Bool?
|
||||||
|
|
||||||
/// :nodoc:
|
// MARK: Server
|
||||||
public init(ca: CryptoContainer) {
|
|
||||||
cipher = .aes128cbc
|
/// The auth-token returned by the server.
|
||||||
digest = .sha1
|
public var authToken: String?
|
||||||
self.ca = ca
|
|
||||||
clientCertificate = nil
|
/// The peer-id returned by the server.
|
||||||
clientKey = nil
|
public var peerId: UInt32?
|
||||||
checksEKU = false
|
|
||||||
compressionFraming = .disabled
|
// MARK: Routing
|
||||||
compressionAlgorithm = .disabled
|
|
||||||
tlsWrap = nil
|
/// The settings for IPv4. `SessionProxy` only evaluates this server-side.
|
||||||
keepAliveInterval = nil
|
public var ipv4: IPv4Settings?
|
||||||
renegotiatesAfter = nil
|
|
||||||
dnsServers = nil
|
/// The settings for IPv6. `SessionProxy` only evaluates this server-side.
|
||||||
searchDomain = nil
|
public var ipv6: IPv6Settings?
|
||||||
randomizeEndpoint = false
|
|
||||||
usesPIAPatches = false
|
/// The DNS servers.
|
||||||
}
|
public var dnsServers: [String]?
|
||||||
|
|
||||||
|
/// The search domain.
|
||||||
|
public var searchDomain: String?
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Builds a `SessionProxy.Configuration` object.
|
Builds a `SessionProxy.Configuration` object.
|
||||||
|
|
||||||
|
@ -208,34 +228,63 @@ extension SessionProxy {
|
||||||
return Configuration(
|
return Configuration(
|
||||||
cipher: cipher,
|
cipher: cipher,
|
||||||
digest: digest,
|
digest: digest,
|
||||||
|
compressionFraming: compressionFraming,
|
||||||
|
compressionAlgorithm: compressionAlgorithm,
|
||||||
ca: ca,
|
ca: ca,
|
||||||
clientCertificate: clientCertificate,
|
clientCertificate: clientCertificate,
|
||||||
clientKey: clientKey,
|
clientKey: clientKey,
|
||||||
checksEKU: checksEKU,
|
|
||||||
compressionFraming: compressionFraming,
|
|
||||||
compressionAlgorithm: compressionAlgorithm,
|
|
||||||
tlsWrap: tlsWrap,
|
tlsWrap: tlsWrap,
|
||||||
keepAliveInterval: keepAliveInterval,
|
keepAliveInterval: keepAliveInterval,
|
||||||
renegotiatesAfter: renegotiatesAfter,
|
renegotiatesAfter: renegotiatesAfter,
|
||||||
dnsServers: dnsServers,
|
hostname: hostname,
|
||||||
searchDomain: searchDomain,
|
endpointProtocols: endpointProtocols,
|
||||||
|
checksEKU: checksEKU,
|
||||||
randomizeEndpoint: randomizeEndpoint,
|
randomizeEndpoint: randomizeEndpoint,
|
||||||
usesPIAPatches: usesPIAPatches
|
usesPIAPatches: usesPIAPatches,
|
||||||
|
authToken: authToken,
|
||||||
|
peerId: peerId,
|
||||||
|
ipv4: ipv4,
|
||||||
|
ipv6: ipv6,
|
||||||
|
dnsServers: dnsServers,
|
||||||
|
searchDomain: searchDomain
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Shortcuts
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var fallbackCipher: Cipher {
|
||||||
|
return cipher ?? Fallback.cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var fallbackDigest: Digest {
|
||||||
|
return digest ?? Fallback.digest
|
||||||
|
}
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var fallbackCompressionFraming: CompressionFraming {
|
||||||
|
return compressionFraming ?? Fallback.compressionFraming
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The immutable configuration for `SessionProxy`.
|
/// The immutable configuration for `SessionProxy`.
|
||||||
public struct Configuration: Codable, Equatable {
|
public struct Configuration: Codable {
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.cipher`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.cipher`
|
||||||
public let cipher: Cipher
|
public let cipher: Cipher?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.digest`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.digest`
|
||||||
public let digest: Digest
|
public let digest: Digest?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.compressionFraming`
|
||||||
|
public let compressionFraming: CompressionFraming?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.compressionAlgorithm`
|
||||||
|
public let compressionAlgorithm: CompressionAlgorithm?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.ca`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.ca`
|
||||||
public let ca: CryptoContainer
|
public let ca: CryptoContainer?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificate`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificate`
|
||||||
public let clientCertificate: CryptoContainer?
|
public let clientCertificate: CryptoContainer?
|
||||||
|
@ -243,15 +292,6 @@ extension SessionProxy {
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.clientKey`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.clientKey`
|
||||||
public let clientKey: CryptoContainer?
|
public let clientKey: CryptoContainer?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.checksEKU`
|
|
||||||
public let checksEKU: Bool?
|
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.compressionFraming`
|
|
||||||
public let compressionFraming: CompressionFraming
|
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.compressionAlgorithm`
|
|
||||||
public let compressionAlgorithm: CompressionAlgorithm?
|
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.tlsWrap`
|
||||||
public var tlsWrap: TLSWrap?
|
public var tlsWrap: TLSWrap?
|
||||||
|
|
||||||
|
@ -261,11 +301,14 @@ extension SessionProxy {
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.renegotiatesAfter`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.renegotiatesAfter`
|
||||||
public let renegotiatesAfter: TimeInterval?
|
public let renegotiatesAfter: TimeInterval?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.dnsServers`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.hostname`
|
||||||
public let dnsServers: [String]?
|
public var hostname: String?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.searchDomain`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.endpointProtocols`
|
||||||
public let searchDomain: String?
|
public var endpointProtocols: [EndpointProtocol]?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.checksEKU`
|
||||||
|
public let checksEKU: Bool?
|
||||||
|
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.randomizeEndpoint`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.randomizeEndpoint`
|
||||||
public let randomizeEndpoint: Bool?
|
public let randomizeEndpoint: Bool?
|
||||||
|
@ -273,49 +316,183 @@ extension SessionProxy {
|
||||||
/// - Seealso: `SessionProxy.ConfigurationBuilder.usesPIAPatches`
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.usesPIAPatches`
|
||||||
public let usesPIAPatches: Bool?
|
public let usesPIAPatches: Bool?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.authToken`
|
||||||
|
public let authToken: String?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.peerId`
|
||||||
|
public let peerId: UInt32?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.ipv4`
|
||||||
|
public let ipv4: IPv4Settings?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.ipv6`
|
||||||
|
public let ipv6: IPv6Settings?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.dnsServers`
|
||||||
|
public let dnsServers: [String]?
|
||||||
|
|
||||||
|
/// - Seealso: `SessionProxy.ConfigurationBuilder.searchDomain`
|
||||||
|
public let searchDomain: String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns a `SessionProxy.ConfigurationBuilder` to use this configuration as a starting point for a new one.
|
Returns a `SessionProxy.ConfigurationBuilder` to use this configuration as a starting point for a new one.
|
||||||
|
|
||||||
- Returns: An editable `SessionProxy.ConfigurationBuilder` initialized with this configuration.
|
- Returns: An editable `SessionProxy.ConfigurationBuilder` initialized with this configuration.
|
||||||
*/
|
*/
|
||||||
public func builder() -> SessionProxy.ConfigurationBuilder {
|
public func builder() -> SessionProxy.ConfigurationBuilder {
|
||||||
var builder = SessionProxy.ConfigurationBuilder(ca: ca)
|
var builder = SessionProxy.ConfigurationBuilder()
|
||||||
builder.cipher = cipher
|
builder.cipher = cipher
|
||||||
builder.digest = digest
|
builder.digest = digest
|
||||||
builder.clientCertificate = clientCertificate
|
|
||||||
builder.clientKey = clientKey
|
|
||||||
builder.checksEKU = checksEKU
|
|
||||||
builder.compressionFraming = compressionFraming
|
builder.compressionFraming = compressionFraming
|
||||||
builder.compressionAlgorithm = compressionAlgorithm
|
builder.compressionAlgorithm = compressionAlgorithm
|
||||||
|
builder.ca = ca
|
||||||
|
builder.clientCertificate = clientCertificate
|
||||||
|
builder.clientKey = clientKey
|
||||||
builder.tlsWrap = tlsWrap
|
builder.tlsWrap = tlsWrap
|
||||||
builder.keepAliveInterval = keepAliveInterval
|
builder.keepAliveInterval = keepAliveInterval
|
||||||
builder.renegotiatesAfter = renegotiatesAfter
|
builder.renegotiatesAfter = renegotiatesAfter
|
||||||
builder.dnsServers = dnsServers
|
builder.endpointProtocols = endpointProtocols
|
||||||
builder.searchDomain = searchDomain
|
builder.checksEKU = checksEKU
|
||||||
builder.randomizeEndpoint = randomizeEndpoint
|
builder.randomizeEndpoint = randomizeEndpoint
|
||||||
builder.usesPIAPatches = usesPIAPatches
|
builder.usesPIAPatches = usesPIAPatches
|
||||||
|
builder.authToken = authToken
|
||||||
|
builder.peerId = peerId
|
||||||
|
builder.ipv4 = ipv4
|
||||||
|
builder.ipv6 = ipv6
|
||||||
|
builder.dnsServers = dnsServers
|
||||||
|
builder.searchDomain = searchDomain
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Equatable
|
// MARK: Shortcuts
|
||||||
|
|
||||||
/// :nodoc:
|
/// :nodoc:
|
||||||
public static func ==(lhs: Configuration, rhs: Configuration) -> Bool {
|
public var fallbackCipher: Cipher {
|
||||||
return
|
return cipher ?? Fallback.cipher
|
||||||
(lhs.cipher == rhs.cipher) &&
|
}
|
||||||
(lhs.digest == rhs.digest) &&
|
|
||||||
(lhs.ca == rhs.ca) &&
|
/// :nodoc:
|
||||||
(lhs.clientCertificate == rhs.clientCertificate) &&
|
public var fallbackDigest: Digest {
|
||||||
(lhs.clientKey == rhs.clientKey) &&
|
return digest ?? Fallback.digest
|
||||||
(lhs.checksEKU == rhs.checksEKU) &&
|
}
|
||||||
(lhs.compressionFraming == rhs.compressionFraming) &&
|
|
||||||
(lhs.compressionAlgorithm == rhs.compressionAlgorithm) &&
|
/// :nodoc:
|
||||||
(lhs.keepAliveInterval == rhs.keepAliveInterval) &&
|
public var fallbackCompressionFraming: CompressionFraming {
|
||||||
(lhs.renegotiatesAfter == rhs.renegotiatesAfter) &&
|
return compressionFraming ?? Fallback.compressionFraming
|
||||||
(lhs.dnsServers == rhs.dnsServers) &&
|
|
||||||
(lhs.searchDomain == rhs.searchDomain) &&
|
|
||||||
(lhs.randomizeEndpoint == rhs.randomizeEndpoint) &&
|
|
||||||
(lhs.usesPIAPatches == rhs.usesPIAPatches)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encapsulates the IPv4 settings for the tunnel.
|
||||||
|
public struct IPv4Settings: Codable, CustomStringConvertible {
|
||||||
|
|
||||||
|
/// Represents an IPv4 route in the routing table.
|
||||||
|
public struct Route: Codable, CustomStringConvertible {
|
||||||
|
|
||||||
|
/// The destination host or subnet.
|
||||||
|
public let destination: String
|
||||||
|
|
||||||
|
/// The address mask.
|
||||||
|
public let mask: String
|
||||||
|
|
||||||
|
/// The address of the gateway (uses default gateway if not set).
|
||||||
|
public let gateway: String
|
||||||
|
|
||||||
|
init(_ destination: String, _ mask: String?, _ gateway: String) {
|
||||||
|
self.destination = destination
|
||||||
|
self.mask = mask ?? "255.255.255.255"
|
||||||
|
self.gateway = gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: CustomStringConvertible
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var description: String {
|
||||||
|
return "{\(destination.maskedDescription)/\(mask) \(gateway.maskedDescription)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The address.
|
||||||
|
let address: String
|
||||||
|
|
||||||
|
/// The address mask.
|
||||||
|
let addressMask: String
|
||||||
|
|
||||||
|
/// The address of the default gateway.
|
||||||
|
let defaultGateway: String
|
||||||
|
|
||||||
|
/// The additional routes.
|
||||||
|
let routes: [Route]
|
||||||
|
|
||||||
|
// MARK: CustomStringConvertible
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var description: String {
|
||||||
|
return "addr \(address.maskedDescription) netmask \(addressMask) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encapsulates the IPv6 settings for the tunnel.
|
||||||
|
public struct IPv6Settings: Codable, CustomStringConvertible {
|
||||||
|
|
||||||
|
/// Represents an IPv6 route in the routing table.
|
||||||
|
public struct Route: Codable, CustomStringConvertible {
|
||||||
|
|
||||||
|
/// The destination host or subnet.
|
||||||
|
public let destination: String
|
||||||
|
|
||||||
|
/// The address prefix length.
|
||||||
|
public let prefixLength: UInt8
|
||||||
|
|
||||||
|
/// The address of the gateway (uses default gateway if not set).
|
||||||
|
public let gateway: String
|
||||||
|
|
||||||
|
init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String) {
|
||||||
|
self.destination = destination
|
||||||
|
self.prefixLength = prefixLength ?? 3
|
||||||
|
self.gateway = gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: CustomStringConvertible
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var description: String {
|
||||||
|
return "{\(destination.maskedDescription)/\(prefixLength) \(gateway.maskedDescription)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The address.
|
||||||
|
public let address: String
|
||||||
|
|
||||||
|
/// The address prefix length.
|
||||||
|
public let addressPrefixLength: UInt8
|
||||||
|
|
||||||
|
/// The address of the default gateway.
|
||||||
|
public let defaultGateway: String
|
||||||
|
|
||||||
|
/// The additional routes.
|
||||||
|
public let routes: [Route]
|
||||||
|
|
||||||
|
// MARK: CustomStringConvertible
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
public var description: String {
|
||||||
|
return "addr \(address.maskedDescription)/\(addressPrefixLength) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// :nodoc:
|
||||||
|
extension EndpointProtocol: Codable {
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
guard let proto = try EndpointProtocol(rawValue: container.decode(String.self)) else {
|
||||||
|
throw ConfigurationError.malformed(option: "remote/proto")
|
||||||
|
}
|
||||||
|
self.init(proto.socketType, proto.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
try container.encode(rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,19 +41,16 @@ import Foundation
|
||||||
public protocol SessionReply {
|
public protocol SessionReply {
|
||||||
|
|
||||||
/// The returned options.
|
/// The returned options.
|
||||||
var options: OptionsBundle { get }
|
var options: SessionProxy.Configuration { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SessionProxy {
|
extension SessionProxy {
|
||||||
|
|
||||||
// XXX: parsing is very optimistic
|
|
||||||
|
|
||||||
struct PushReply: SessionReply, CustomStringConvertible {
|
struct PushReply: SessionReply, CustomStringConvertible {
|
||||||
private static let prefix = "PUSH_REPLY,"
|
private static let prefix = "PUSH_REPLY,"
|
||||||
|
|
||||||
private let original: String
|
private let original: String
|
||||||
|
|
||||||
let options: OptionsBundle
|
let options: SessionProxy.Configuration
|
||||||
|
|
||||||
init?(message: String) throws {
|
init?(message: String) throws {
|
||||||
guard message.hasPrefix(PushReply.prefix) else {
|
guard message.hasPrefix(PushReply.prefix) else {
|
||||||
|
@ -65,14 +62,14 @@ extension SessionProxy {
|
||||||
original = String(message[prefixIndex...])
|
original = String(message[prefixIndex...])
|
||||||
|
|
||||||
let lines = original.components(separatedBy: ",")
|
let lines = original.components(separatedBy: ",")
|
||||||
options = try OptionsBundle(from: lines)
|
options = try ConfigurationParser.parsed(fromLines: lines).configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: CustomStringConvertible
|
// MARK: CustomStringConvertible
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
let stripped = NSMutableString(string: original)
|
let stripped = NSMutableString(string: original)
|
||||||
OptionsBundle.Regex.authToken.replaceMatches(
|
ConfigurationParser.Regex.authToken.replaceMatches(
|
||||||
in: stripped,
|
in: stripped,
|
||||||
options: [],
|
options: [],
|
||||||
range: NSMakeRange(0, stripped.length),
|
range: NSMakeRange(0, stripped.length),
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class SessionProxy {
|
||||||
|
|
||||||
private var keepAliveInterval: TimeInterval? {
|
private var keepAliveInterval: TimeInterval? {
|
||||||
let interval: TimeInterval?
|
let interval: TimeInterval?
|
||||||
if let negInterval = pushReply?.options.keepAliveSeconds, negInterval > 0 {
|
if let negInterval = pushReply?.options.keepAliveInterval, negInterval > 0 {
|
||||||
interval = TimeInterval(negInterval)
|
interval = TimeInterval(negInterval)
|
||||||
} else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 {
|
} else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 {
|
||||||
interval = cfgInterval
|
interval = cfgInterval
|
||||||
|
@ -179,6 +179,10 @@ public class SessionProxy {
|
||||||
- Parameter configuration: The `SessionProxy.Configuration` to use for this session.
|
- Parameter configuration: The `SessionProxy.Configuration` to use for this session.
|
||||||
*/
|
*/
|
||||||
public init(queue: DispatchQueue, configuration: Configuration, cachesURL: URL) throws {
|
public init(queue: DispatchQueue, configuration: Configuration, cachesURL: URL) throws {
|
||||||
|
guard let ca = configuration.ca else {
|
||||||
|
throw ConfigurationError.missingConfiguration(option: "ca")
|
||||||
|
}
|
||||||
|
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.cachesURL = cachesURL
|
self.cachesURL = cachesURL
|
||||||
|
@ -192,7 +196,7 @@ public class SessionProxy {
|
||||||
if let tlsWrap = configuration.tlsWrap {
|
if let tlsWrap = configuration.tlsWrap {
|
||||||
switch tlsWrap.strategy {
|
switch tlsWrap.strategy {
|
||||||
case .auth:
|
case .auth:
|
||||||
controlChannel = try ControlChannel(withAuthKey: tlsWrap.key, digest: configuration.digest)
|
controlChannel = try ControlChannel(withAuthKey: tlsWrap.key, digest: configuration.fallbackDigest)
|
||||||
|
|
||||||
case .crypt:
|
case .crypt:
|
||||||
controlChannel = try ControlChannel(withCryptKey: tlsWrap.key)
|
controlChannel = try ControlChannel(withCryptKey: tlsWrap.key)
|
||||||
|
@ -203,7 +207,7 @@ public class SessionProxy {
|
||||||
|
|
||||||
// cache PEMs locally (mandatory for OpenSSL)
|
// cache PEMs locally (mandatory for OpenSSL)
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
try configuration.ca.pem.write(to: caURL, atomically: true, encoding: .ascii)
|
try ca.pem.write(to: caURL, atomically: true, encoding: .ascii)
|
||||||
if let container = configuration.clientCertificate {
|
if let container = configuration.clientCertificate {
|
||||||
try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii)
|
try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii)
|
||||||
} else {
|
} else {
|
||||||
|
@ -630,8 +634,8 @@ public class SessionProxy {
|
||||||
log.debug("CA MD5 is: \(caMD5)")
|
log.debug("CA MD5 is: \(caMD5)")
|
||||||
return try? PIAHardReset(
|
return try? PIAHardReset(
|
||||||
caMd5Digest: caMD5,
|
caMd5Digest: caMD5,
|
||||||
cipher: configuration.cipher,
|
cipher: configuration.fallbackCipher,
|
||||||
digest: configuration.digest
|
digest: configuration.fallbackDigest
|
||||||
).encodedData()
|
).encodedData()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1036,6 +1040,10 @@ public class SessionProxy {
|
||||||
log.debug("Set up encryption")
|
log.debug("Set up encryption")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pushedCipher = pushReply.options.cipher
|
||||||
|
if let negCipher = pushedCipher {
|
||||||
|
log.info("\tNegotiated cipher: \(negCipher.rawValue)")
|
||||||
|
}
|
||||||
let pushedFraming = pushReply.options.compressionFraming
|
let pushedFraming = pushReply.options.compressionFraming
|
||||||
if let negFraming = pushedFraming {
|
if let negFraming = pushedFraming {
|
||||||
log.info("\tNegotiated compression framing: \(negFraming)")
|
log.info("\tNegotiated compression framing: \(negFraming)")
|
||||||
|
@ -1044,19 +1052,15 @@ public class SessionProxy {
|
||||||
if let negCompression = pushedCompression {
|
if let negCompression = pushedCompression {
|
||||||
log.info("\tNegotiated compression algorithm: \(negCompression)")
|
log.info("\tNegotiated compression algorithm: \(negCompression)")
|
||||||
}
|
}
|
||||||
if let negPing = pushReply.options.keepAliveSeconds {
|
if let negPing = pushReply.options.keepAliveInterval {
|
||||||
log.info("\tNegotiated keep-alive: \(negPing) seconds")
|
log.info("\tNegotiated keep-alive: \(negPing) seconds")
|
||||||
}
|
}
|
||||||
let pushedCipher = pushReply.options.cipher
|
|
||||||
if let negCipher = pushedCipher {
|
|
||||||
log.info("\tNegotiated cipher: \(negCipher.rawValue)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let bridge: EncryptionBridge
|
let bridge: EncryptionBridge
|
||||||
do {
|
do {
|
||||||
bridge = try EncryptionBridge(
|
bridge = try EncryptionBridge(
|
||||||
pushedCipher ?? configuration.cipher,
|
pushedCipher ?? configuration.fallbackCipher,
|
||||||
configuration.digest,
|
configuration.fallbackDigest,
|
||||||
auth,
|
auth,
|
||||||
sessionId,
|
sessionId,
|
||||||
remoteSessionId
|
remoteSessionId
|
||||||
|
@ -1070,7 +1074,7 @@ public class SessionProxy {
|
||||||
encrypter: bridge.encrypter(),
|
encrypter: bridge.encrypter(),
|
||||||
decrypter: bridge.decrypter(),
|
decrypter: bridge.decrypter(),
|
||||||
peerId: pushReply.options.peerId ?? PacketPeerIdDisabled,
|
peerId: pushReply.options.peerId ?? PacketPeerIdDisabled,
|
||||||
compressionFraming: (pushedFraming ?? configuration.compressionFraming).native,
|
compressionFraming: (pushedFraming ?? configuration.fallbackCompressionFraming).native,
|
||||||
compressionAlgorithm: (pushedCompression ?? configuration.compressionAlgorithm ?? .disabled).native,
|
compressionAlgorithm: (pushedCompression ?? configuration.compressionAlgorithm ?? .disabled).native,
|
||||||
maxPackets: link?.packetBufferSize ?? 200,
|
maxPackets: link?.packetBufferSize ?? 200,
|
||||||
usesReplayProtection: CoreConfiguration.usesReplayProtection
|
usesReplayProtection: CoreConfiguration.usesReplayProtection
|
||||||
|
|
|
@ -60,9 +60,11 @@ class AppExtensionTests: XCTestCase {
|
||||||
let hostname = "example.com"
|
let hostname = "example.com"
|
||||||
let credentials = SessionProxy.Credentials("foo", "bar")
|
let credentials = SessionProxy.Credentials("foo", "bar")
|
||||||
|
|
||||||
var sessionBuilder = SessionProxy.ConfigurationBuilder(ca: CryptoContainer(pem: "abcdef"))
|
var sessionBuilder = SessionProxy.ConfigurationBuilder()
|
||||||
|
sessionBuilder.ca = CryptoContainer(pem: "abcdef")
|
||||||
sessionBuilder.cipher = .aes128cbc
|
sessionBuilder.cipher = .aes128cbc
|
||||||
sessionBuilder.digest = .sha256
|
sessionBuilder.digest = .sha256
|
||||||
|
sessionBuilder.endpointProtocols = []
|
||||||
builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
builder = TunnelKitProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build())
|
||||||
XCTAssertNotNil(builder)
|
XCTAssertNotNil(builder)
|
||||||
|
|
||||||
|
@ -82,9 +84,9 @@ class AppExtensionTests: XCTestCase {
|
||||||
|
|
||||||
let K = TunnelKitProvider.Configuration.Keys.self
|
let K = TunnelKitProvider.Configuration.Keys.self
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.appGroup] as? String, appGroup)
|
XCTAssertEqual(proto?.providerConfiguration?[K.appGroup] as? String, appGroup)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.cipherAlgorithm] as? String, cfg.sessionConfiguration.cipher.rawValue)
|
XCTAssertEqual(proto?.providerConfiguration?[K.cipherAlgorithm] as? String, cfg.sessionConfiguration.cipher?.rawValue)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.digestAlgorithm] as? String, cfg.sessionConfiguration.digest.rawValue)
|
XCTAssertEqual(proto?.providerConfiguration?[K.digestAlgorithm] as? String, cfg.sessionConfiguration.digest?.rawValue)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.ca] as? String, cfg.sessionConfiguration.ca.pem)
|
XCTAssertEqual(proto?.providerConfiguration?[K.ca] as? String, cfg.sessionConfiguration.ca?.pem)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.mtu] as? Int, cfg.mtu)
|
XCTAssertEqual(proto?.providerConfiguration?[K.mtu] as? Int, cfg.mtu)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.renegotiatesAfter] as? TimeInterval, cfg.sessionConfiguration.renegotiatesAfter)
|
XCTAssertEqual(proto?.providerConfiguration?[K.renegotiatesAfter] as? TimeInterval, cfg.sessionConfiguration.renegotiatesAfter)
|
||||||
XCTAssertEqual(proto?.providerConfiguration?[K.debug] as? Bool, cfg.shouldDebug)
|
XCTAssertEqual(proto?.providerConfiguration?[K.debug] as? Bool, cfg.shouldDebug)
|
||||||
|
|
|
@ -27,6 +27,8 @@ import XCTest
|
||||||
import TunnelKit
|
import TunnelKit
|
||||||
|
|
||||||
class ConfigurationParserTests: XCTestCase {
|
class ConfigurationParserTests: XCTestCase {
|
||||||
|
let base: [String] = ["<ca>", "</ca>", "remote 1.2.3.4"]
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
@ -37,12 +39,41 @@ class ConfigurationParserTests: XCTestCase {
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from lines
|
||||||
|
|
||||||
|
func testCompression() throws {
|
||||||
|
// XCTAssertNotNil(try OptionsBundle.parsed(fromLines: base + ["comp-lzo"]).warning)
|
||||||
|
XCTAssertNil(try ConfigurationParser.parsed(fromLines: base + ["comp-lzo"]).warning)
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["comp-lzo no"]))
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["comp-lzo yes"]))
|
||||||
|
// XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: base + ["comp-lzo yes"]))
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["compress"]))
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["compress lzo"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDHCPOption() throws {
|
||||||
|
let lines = base + ["dhcp-option DNS 8.8.8.8", "dhcp-option DNS6 ffff::1", "dhcp-option DOMAIN example.com"]
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: lines))
|
||||||
|
|
||||||
|
let parsed = try! ConfigurationParser.parsed(fromLines: lines).configuration
|
||||||
|
XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"])
|
||||||
|
XCTAssertEqual(parsed.searchDomain, "example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConnectionBlock() throws {
|
||||||
|
let lines = base + ["<connection>", "</connection>"]
|
||||||
|
XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
// from file
|
||||||
|
|
||||||
func testPIA() throws {
|
func testPIA() throws {
|
||||||
let file = try ConfigurationParser.parsed(fromURL: url(withName: "pia-hungary"))
|
let file = try ConfigurationParser.parsed(fromURL: url(withName: "pia-hungary"))
|
||||||
XCTAssertEqual(file.hostname, "hungary.privateinternetaccess.com")
|
XCTAssertEqual(file.configuration.hostname, "hungary.privateinternetaccess.com")
|
||||||
XCTAssertEqual(file.configuration.cipher, .aes128cbc)
|
XCTAssertEqual(file.configuration.cipher, .aes128cbc)
|
||||||
XCTAssertEqual(file.configuration.digest, .sha1)
|
XCTAssertEqual(file.configuration.digest, .sha1)
|
||||||
XCTAssertEqual(file.protocols, [
|
XCTAssertEqual(file.configuration.endpointProtocols, [
|
||||||
EndpointProtocol(.udp, 1198),
|
EndpointProtocol(.udp, 1198),
|
||||||
EndpointProtocol(.tcp, 502)
|
EndpointProtocol(.tcp, 502)
|
||||||
])
|
])
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
//
|
|
||||||
// OptionsBundleTests.swift
|
|
||||||
// TunnelKitTests
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 4/3/19.
|
|
||||||
// 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/>.
|
|
||||||
//
|
|
||||||
|
|
||||||
import XCTest
|
|
||||||
import TunnelKit
|
|
||||||
|
|
||||||
class OptionsBundleTests: XCTestCase {
|
|
||||||
let base: [String] = ["<ca>", "</ca>", "remote 1.2.3.4"]
|
|
||||||
|
|
||||||
override func setUp() {
|
|
||||||
super.setUp()
|
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDown() {
|
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
|
||||||
super.tearDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCompression() throws {
|
|
||||||
// XCTAssertNotNil(try OptionsBundle.parsed(fromLines: base + ["comp-lzo"]).warning)
|
|
||||||
XCTAssertNil(try OptionsBundle(from: base + ["comp-lzo"]).warning)
|
|
||||||
XCTAssertNoThrow(try OptionsBundle(from: base + ["comp-lzo no"]))
|
|
||||||
XCTAssertNoThrow(try OptionsBundle(from: base + ["comp-lzo yes"]))
|
|
||||||
// XCTAssertThrowsError(try OptionsBundle(from: base + ["comp-lzo yes"]))
|
|
||||||
|
|
||||||
XCTAssertNoThrow(try OptionsBundle(from: base + ["compress"]))
|
|
||||||
XCTAssertNoThrow(try OptionsBundle(from: base + ["compress lzo"]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDHCPOption() throws {
|
|
||||||
let lines = base + ["dhcp-option DNS 8.8.8.8", "dhcp-option DNS6 ffff::1", "dhcp-option DOMAIN example.com"]
|
|
||||||
XCTAssertNoThrow(try OptionsBundle(from: lines))
|
|
||||||
|
|
||||||
let parsed = try! OptionsBundle(from: lines)
|
|
||||||
XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"])
|
|
||||||
XCTAssertEqual(parsed.searchDomain, "example.com")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testConnectionBlock() throws {
|
|
||||||
let lines = base + ["<connection>", "</connection>"]
|
|
||||||
XCTAssertThrowsError(try OptionsBundle(from: lines))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,11 +28,11 @@ import XCTest
|
||||||
|
|
||||||
private extension SessionReply {
|
private extension SessionReply {
|
||||||
func debug() {
|
func debug() {
|
||||||
print("Compression framing: \(options.compressionFraming?.description ?? "none")")
|
print("Compression framing: \(options.compressionFraming?.description ?? "disabled")")
|
||||||
print("Compression algorithm: \(options.compressionAlgorithm?.description ?? "none")")
|
print("Compression algorithm: \(options.compressionAlgorithm?.description ?? "disabled")")
|
||||||
print("IPv4: \(options.ipv4?.description ?? "none")")
|
print("IPv4: \(options.ipv4?.description ?? "none")")
|
||||||
print("IPv6: \(options.ipv6?.description ?? "none")")
|
print("IPv6: \(options.ipv6?.description ?? "none")")
|
||||||
print("DNS: \(options.dnsServers)")
|
print("DNS: \(options.dnsServers?.description ?? "none")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +153,6 @@ class PushTests: XCTestCase {
|
||||||
let reply = try! SessionProxy.PushReply(message: msg)!
|
let reply = try! SessionProxy.PushReply(message: msg)!
|
||||||
reply.debug()
|
reply.debug()
|
||||||
|
|
||||||
XCTAssertEqual(reply.options.keepAliveSeconds, 10)
|
XCTAssertEqual(reply.options.keepAliveInterval, 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue