Merge branch 'http-proxy-settings'

This commit is contained in:
Davide De Rosa 2019-04-13 09:35:08 +02:00
commit 12b26df10d
6 changed files with 141 additions and 5 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Basic support for proxy settings (no PAC). [#74](https://github.com/keeshux/tunnelkit/issues/74)
### Changed ### Changed
- Make `hostname` optional and pick `resolvedAddresses` if nil. - Make `hostname` optional and pick `resolvedAddresses` if nil.

View File

@ -163,6 +163,18 @@ extension TunnelKitProvider {
sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.usesPIAPatches sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.usesPIAPatches
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
if let proxyString = providerConfiguration[S.httpProxy] as? String {
guard let proxy = Proxy(rawValue: proxyString) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.httpProxy)] has a badly formed element")
}
sessionConfigurationBuilder.httpProxy = proxy
}
if let proxyString = providerConfiguration[S.httpsProxy] as? String {
guard let proxy = Proxy(rawValue: proxyString) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.httpsProxy)] has a badly formed element")
}
sessionConfigurationBuilder.httpsProxy = proxy
}
sessionConfiguration = sessionConfigurationBuilder.build() sessionConfiguration = sessionConfigurationBuilder.build()
shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug
@ -240,6 +252,10 @@ extension TunnelKitProvider {
static let searchDomain = "SearchDomain" static let searchDomain = "SearchDomain"
static let httpProxy = "HTTPProxy"
static let httpsProxy = "HTTPSProxy"
// MARK: Debugging // MARK: Debugging
static let debug = "Debug" static let debug = "Debug"
@ -443,6 +459,12 @@ extension TunnelKitProvider {
if let searchDomain = sessionConfiguration.searchDomain { if let searchDomain = sessionConfiguration.searchDomain {
dict[S.searchDomain] = searchDomain dict[S.searchDomain] = searchDomain
} }
if let httpProxy = sessionConfiguration.httpProxy {
dict[S.httpProxy] = httpProxy.rawValue
}
if let httpsProxy = sessionConfiguration.httpsProxy {
dict[S.httpsProxy] = httpsProxy.rawValue
}
// //
if let resolvedAddresses = resolvedAddresses { if let resolvedAddresses = resolvedAddresses {
dict[S.resolvedAddresses] = resolvedAddresses dict[S.resolvedAddresses] = resolvedAddresses
@ -537,6 +559,12 @@ extension TunnelKitProvider {
if let searchDomain = sessionConfiguration.searchDomain { if let searchDomain = sessionConfiguration.searchDomain {
log.info("\tSearch domain: \(searchDomain.maskedDescription)") log.info("\tSearch domain: \(searchDomain.maskedDescription)")
} }
if let httpProxy = sessionConfiguration.httpProxy {
log.info("\tHTTP proxy: \(httpProxy.maskedDescription)")
}
if let httpsProxy = sessionConfiguration.httpsProxy {
log.info("\tHTTPS proxy: \(httpsProxy.maskedDescription)")
}
log.info("\tMTU: \(mtu)") 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)")

View File

@ -555,10 +555,22 @@ extension TunnelKitProvider: SessionProxyDelegate {
dnsSettings.searchDomains = [searchDomain] dnsSettings.searchDomains = [searchDomain]
} }
var proxySettings: NEProxySettings?
if let httpsProxy = cfg.sessionConfiguration.httpsProxy ?? reply.options.httpsProxy {
proxySettings = NEProxySettings()
proxySettings?.httpsServer = httpsProxy.neProxy()
proxySettings?.httpsEnabled = true
} else if let httpProxy = cfg.sessionConfiguration.httpProxy ?? reply.options.httpProxy {
proxySettings = NEProxySettings()
proxySettings?.httpServer = httpProxy.neProxy()
proxySettings?.httpEnabled = true
}
let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
newSettings.ipv4Settings = ipv4Settings newSettings.ipv4Settings = ipv4Settings
newSettings.ipv6Settings = ipv6Settings newSettings.ipv6Settings = ipv6Settings
newSettings.dnsSettings = dnsSettings newSettings.dnsSettings = dnsSettings
newSettings.proxySettings = proxySettings
setTunnelNetworkSettings(newSettings, completionHandler: completionHandler) setTunnelNetworkSettings(newSettings, completionHandler: completionHandler)
} }
@ -671,3 +683,9 @@ extension TunnelKitProvider {
return error as? ProviderError ?? .linkError return error as? ProviderError ?? .linkError
} }
} }
private extension Proxy {
func neProxy() -> NEProxyServer {
return NEProxyServer(address: address, port: Int(port))
}
}

View File

@ -92,12 +92,14 @@ public class ConfigurationParser {
static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+") static let domain = NSRegularExpression("^dhcp-option +DOMAIN +[^ ]+")
static let proxy = NSRegularExpression("^dhcp-option +PROXY_(HTTPS?|BYPASS) +[^ ]+ +\\d+")
// MARK: Unsupported // MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+") // static let fragment = NSRegularExpression("^fragment +\\d+")
static let fragment = NSRegularExpression("^fragment") static let fragment = NSRegularExpression("^fragment")
static let proxy = NSRegularExpression("^\\w+-proxy") static let connectionProxy = NSRegularExpression("^\\w+-proxy")
static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ") static let externalFiles = NSRegularExpression("^(ca|cert|key|tls-auth|tls-crypt) ")
@ -193,6 +195,8 @@ public class ConfigurationParser {
var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway
var optDNSServers: [String] = [] var optDNSServers: [String] = []
var optSearchDomain: String? var optSearchDomain: String?
var optHTTPProxy: Proxy?
var optHTTPSProxy: Proxy?
log.verbose("Configuration file:") log.verbose("Configuration file:")
for line in lines { for line in lines {
@ -215,7 +219,7 @@ public class ConfigurationParser {
Regex.fragment.enumerateComponents(in: line) { (_) in Regex.fragment.enumerateComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment")
} }
Regex.proxy.enumerateComponents(in: line) { (_) in Regex.connectionProxy.enumerateComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
} }
Regex.externalFiles.enumerateComponents(in: line) { (_) in Regex.externalFiles.enumerateComponents(in: line) { (_) in
@ -469,6 +473,21 @@ public class ConfigurationParser {
} }
optSearchDomain = $0[1] optSearchDomain = $0[1]
} }
Regex.proxy.enumerateArguments(in: line) {
guard $0.count == 3, let port = UInt16($0[2]) else {
return
}
switch $0[0] {
case "PROXY_HTTPS":
optHTTPSProxy = Proxy($0[1], port)
case "PROXY_HTTP":
optHTTPProxy = Proxy($0[1], port)
default:
break
}
}
// //
@ -631,6 +650,8 @@ public class ConfigurationParser {
sessionBuilder.dnsServers = optDNSServers sessionBuilder.dnsServers = optDNSServers
sessionBuilder.searchDomain = optSearchDomain sessionBuilder.searchDomain = optSearchDomain
sessionBuilder.httpProxy = optHTTPProxy
sessionBuilder.httpsProxy = optHTTPSProxy
// //

View File

@ -215,6 +215,12 @@ extension SessionProxy {
/// The search domain. /// The search domain.
public var searchDomain: String? public var searchDomain: String?
/// The HTTP proxy.
public var httpProxy: Proxy?
/// The HTTPS proxy.
public var httpsProxy: Proxy?
/// :nodoc: /// :nodoc:
public init() { public init() {
} }
@ -246,7 +252,9 @@ extension SessionProxy {
ipv4: ipv4, ipv4: ipv4,
ipv6: ipv6, ipv6: ipv6,
dnsServers: dnsServers, dnsServers: dnsServers,
searchDomain: searchDomain searchDomain: searchDomain,
httpProxy: httpProxy,
httpsProxy: httpsProxy
) )
} }
@ -334,6 +342,12 @@ extension SessionProxy {
/// - Seealso: `SessionProxy.ConfigurationBuilder.searchDomain` /// - Seealso: `SessionProxy.ConfigurationBuilder.searchDomain`
public let searchDomain: String? public let searchDomain: String?
/// - Seealso: `SessionProxy.ConfigurationBuilder.httpProxy`
public var httpProxy: Proxy?
/// - Seealso: `SessionProxy.ConfigurationBuilder.httpsProxy`
public var httpsProxy: Proxy?
// MARK: Shortcuts // MARK: Shortcuts
/// :nodoc: /// :nodoc:
@ -384,6 +398,8 @@ extension SessionProxy.Configuration {
builder.ipv6 = ipv6 builder.ipv6 = ipv6
builder.dnsServers = dnsServers builder.dnsServers = dnsServers
builder.searchDomain = searchDomain builder.searchDomain = searchDomain
builder.httpProxy = httpProxy
builder.httpsProxy = httpsProxy
return builder return builder
} }
} }
@ -486,6 +502,45 @@ public struct IPv6Settings: Codable, CustomStringConvertible {
} }
} }
/// Encapsulate a proxy setting.
public struct Proxy: Codable, RawRepresentable, CustomStringConvertible {
/// The proxy address.
public let address: String
/// The proxy port.
public let port: UInt16
/// :nodoc:
public init(_ address: String, _ port: UInt16) {
self.address = address
self.port = port
}
// MARK: RawRepresentable
/// :nodoc:
public var rawValue: String {
return "\(address):\(port)"
}
/// :nodoc:
public init?(rawValue: String) {
let comps = rawValue.components(separatedBy: ":")
guard comps.count == 2, let port = UInt16(comps[1]) else {
return nil
}
self.init(comps[0], port)
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return rawValue
}
}
/// :nodoc: /// :nodoc:
extension EndpointProtocol: Codable { extension EndpointProtocol: Codable {
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {

View File

@ -53,12 +53,22 @@ class ConfigurationParserTests: XCTestCase {
} }
func testDHCPOption() throws { func testDHCPOption() throws {
let lines = base + ["dhcp-option DNS 8.8.8.8", "dhcp-option DNS6 ffff::1", "dhcp-option DOMAIN example.com"] let lines = base + [
"dhcp-option DNS 8.8.8.8",
"dhcp-option DNS6 ffff::1",
"dhcp-option DOMAIN example.com",
"dhcp-option PROXY_HTTP 1.2.3.4 8081",
"dhcp-option PROXY_HTTPS 7.8.9.10 8082"
]
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: lines)) XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: lines))
let parsed = try! ConfigurationParser.parsed(fromLines: lines).configuration let parsed = try! ConfigurationParser.parsed(fromLines: lines).configuration
XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"]) XCTAssertEqual(parsed.dnsServers, ["8.8.8.8", "ffff::1"])
XCTAssertEqual(parsed.searchDomain, "example.com") XCTAssertEqual(parsed.searchDomain, "example.com")
XCTAssertEqual(parsed.httpProxy?.address, "1.2.3.4")
XCTAssertEqual(parsed.httpProxy?.port, 8081)
XCTAssertEqual(parsed.httpsProxy?.address, "7.8.9.10")
XCTAssertEqual(parsed.httpsProxy?.port, 8082)
} }
func testConnectionBlock() throws { func testConnectionBlock() throws {