Merge branch 'routing-policies'

This commit is contained in:
Davide De Rosa 2019-04-25 16:07:11 +02:00
commit e17c5d0fdd
7 changed files with 128 additions and 23 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
- Do not redirect all traffic to VPN unless `--redirect-gateway` specified. [#90](https://github.com/keeshux/tunnelkit/issues/90)
### Fixed
- SoftEther sends an incomplete PUSH_REPLY. [#86](https://github.com/keeshux/tunnelkit/issues/86)

View File

@ -176,6 +176,14 @@ extension TunnelKitProvider {
sessionConfigurationBuilder.httpsProxy = proxy
}
sessionConfigurationBuilder.proxyBypassDomains = providerConfiguration[S.proxyBypassDomains] as? [String]
if let routingPoliciesStrings = providerConfiguration[S.routingPolicies] as? [String], !routingPoliciesStrings.isEmpty {
sessionConfigurationBuilder.routingPolicies = try routingPoliciesStrings.map {
guard let policy = SessionProxy.RoutingPolicy(rawValue: $0) else {
throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.routingPolicies)] has a badly formed element")
}
return policy
}
}
sessionConfiguration = sessionConfigurationBuilder.build()
shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug
@ -259,6 +267,8 @@ extension TunnelKitProvider {
static let proxyBypassDomains = "ProxyBypassDomains"
static let routingPolicies = "RoutingPolicies"
// MARK: Debugging
static let debug = "Debug"
@ -471,6 +481,9 @@ extension TunnelKitProvider {
if let proxyBypassDomains = sessionConfiguration.proxyBypassDomains {
dict[S.proxyBypassDomains] = proxyBypassDomains
}
if let routingPolicies = sessionConfiguration.routingPolicies {
dict[S.routingPolicies] = routingPolicies.map { $0.rawValue }
}
//
if let resolvedAddresses = resolvedAddresses {
dict[S.resolvedAddresses] = resolvedAddresses
@ -559,6 +572,12 @@ extension TunnelKitProvider {
if sessionConfiguration.randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
// FIXME: refine logging of other routing policies
if let routingPolicies = sessionConfiguration.routingPolicies {
log.info("\tDefault gateway: \(routingPolicies.map { $0.rawValue })")
} else {
log.info("\tDefault gateway: no")
}
if let dnsServers = sessionConfiguration.dnsServers {
log.info("\tDNS servers: \(dnsServers.maskedDescription)")
}

View File

@ -477,6 +477,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
} else {
log.info("\tDNS: not configured")
}
log.info("\tRouting policies: \(reply.options.routingPolicies?.maskedDescription ?? "not configured")")
log.info("\tDomain: \(reply.options.searchDomain?.maskedDescription ?? "not configured")")
if reply.options.httpProxy != nil || reply.options.httpsProxy != nil {
@ -492,7 +493,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
}
}
bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in
bringNetworkUp(remoteAddress: remoteAddress, configuration: proxy.configuration, reply: reply) { (error) in
if let error = error {
log.error("Failed to configure tunnel: \(error)")
self.pendingStartHandler?(error)
@ -523,15 +524,20 @@ extension TunnelKitProvider: SessionProxyDelegate {
socket?.shutdown()
}
private func bringNetworkUp(remoteAddress: String, reply: SessionReply, completionHandler: @escaping (Error?) -> Void) {
private func bringNetworkUp(remoteAddress: String, configuration: SessionProxy.Configuration, reply: SessionReply, completionHandler: @escaping (Error?) -> Void) {
let routingPolicies = configuration.routingPolicies ?? reply.options.routingPolicies
// route all traffic to VPN
var ipv4Settings: NEIPv4Settings?
if let ipv4 = reply.options.ipv4 {
let defaultRoute = NEIPv4Route.default()
defaultRoute.gatewayAddress = ipv4.defaultGateway
var routes: [NEIPv4Route] = []
// route all traffic to VPN?
if routingPolicies?.contains(.IPv4) ?? false {
let defaultRoute = NEIPv4Route.default()
defaultRoute.gatewayAddress = ipv4.defaultGateway
routes.append(defaultRoute)
}
var routes: [NEIPv4Route] = [defaultRoute]
for r in ipv4.routes {
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
ipv4Route.gatewayAddress = r.gateway
@ -545,10 +551,15 @@ extension TunnelKitProvider: SessionProxyDelegate {
var ipv6Settings: NEIPv6Settings?
if let ipv6 = reply.options.ipv6 {
let defaultRoute = NEIPv6Route.default()
defaultRoute.gatewayAddress = ipv6.defaultGateway
var routes: [NEIPv6Route] = []
// route all traffic to VPN?
if routingPolicies?.contains(.IPv6) ?? false {
let defaultRoute = NEIPv6Route.default()
defaultRoute.gatewayAddress = ipv6.defaultGateway
routes.append(defaultRoute)
}
var routes: [NEIPv6Route] = [defaultRoute]
for r in ipv6.routes {
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
ipv6Route.gatewayAddress = r.gateway
@ -556,7 +567,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
}
ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber])
ipv6Settings?.includedRoutes = [defaultRoute]
ipv6Settings?.includedRoutes = routes
ipv6Settings?.excludedRoutes = []
}

View File

@ -96,6 +96,8 @@ public class ConfigurationParser {
static let proxyBypass = NSRegularExpression("^dhcp-option +PROXY_BYPASS +.+")
static let redirectGateway = NSRegularExpression("^redirect-gateway.*")
// MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+")
@ -120,6 +122,26 @@ public class ConfigurationParser {
case subnet
}
private enum RedirectGateway: String {
case local
case autolocal
case def1
case bypassDHCP = "bypass-dhcp"
case bypassDNS = "bypass-dns"
case blockLocal = "block-local"
case ipv4
case noIPv4 = "!ipv4"
case ipv6
}
/// Result of the parser.
public struct Result {
@ -204,6 +226,7 @@ public class ConfigurationParser {
var optHTTPProxy: Proxy?
var optHTTPSProxy: Proxy?
var optProxyBypass: [String]?
var optRedirectGateway: Set<RedirectGateway>?
log.verbose("Configuration file:")
for line in lines {
@ -515,6 +538,22 @@ public class ConfigurationParser {
optProxyBypass = $0
optProxyBypass?.removeFirst()
}
Regex.redirectGateway.enumerateArguments(in: line) {
// redirect IPv4 by default
optRedirectGateway = [.ipv4]
for arg in $0 {
guard let opt = RedirectGateway(rawValue: arg) else {
continue
}
if opt == .noIPv4 {
optRedirectGateway?.remove(.ipv4)
} else {
optRedirectGateway?.insert(opt)
}
}
}
//
@ -681,6 +720,11 @@ public class ConfigurationParser {
sessionBuilder.httpsProxy = optHTTPSProxy
sessionBuilder.proxyBypassDomains = optProxyBypass
// FIXME: only redirects all traffic until --redirect-gateway is properly interpreted
if let _ = optRedirectGateway {
sessionBuilder.routingPolicies = [.IPv4, .IPv6]
}
//
return Result(

View File

@ -146,6 +146,16 @@ extension SessionProxy {
}
}
/// Routing policy.
public enum RoutingPolicy: String, Codable {
/// All IPv4 traffic goes through the VPN.
case IPv4
/// All IPv6 traffic goes through the VPN.
case IPv6
}
/// :nodoc:
private struct Fallback {
static let cipher: Cipher = .aes128cbc
@ -238,6 +248,9 @@ extension SessionProxy {
/// The list of domains not passing through the proxy.
public var proxyBypassDomains: [String]?
/// Policies for redirecting traffic through the VPN gateway.
public var routingPolicies: [RoutingPolicy]?
/// :nodoc:
public init() {
}
@ -272,7 +285,8 @@ extension SessionProxy {
searchDomain: searchDomain,
httpProxy: httpProxy,
httpsProxy: httpsProxy,
proxyBypassDomains: proxyBypassDomains
proxyBypassDomains: proxyBypassDomains,
routingPolicies: routingPolicies
)
}
@ -369,6 +383,9 @@ extension SessionProxy {
/// - Seealso: `SessionProxy.ConfigurationBuilder.proxyBypassDomains`
public var proxyBypassDomains: [String]?
/// - Seealso: `SessionProxy.ConfigurationBuilder.routingPolicies`
public var routingPolicies: [RoutingPolicy]?
// MARK: Shortcuts
/// :nodoc:
@ -422,6 +439,7 @@ extension SessionProxy.Configuration {
builder.httpProxy = httpProxy
builder.httpsProxy = httpsProxy
builder.proxyBypassDomains = proxyBypassDomains
builder.routingPolicies = routingPolicies
return builder
}
}

View File

@ -79,7 +79,8 @@ public class SessionProxy {
// MARK: Configuration
private let configuration: Configuration
/// The session base configuration.
public let configuration: Configuration
/// The optional credentials.
public var credentials: Credentials?

View File

@ -27,8 +27,6 @@ import XCTest
import TunnelKit
class ConfigurationParserTests: 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.
@ -42,18 +40,18 @@ class ConfigurationParserTests: XCTestCase {
// 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"]))
// XCTAssertNotNil(try OptionsBundle.parsed(fromLines: ["comp-lzo"]).warning)
XCTAssertNil(try ConfigurationParser.parsed(fromLines: ["comp-lzo"]).warning)
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["comp-lzo no"]))
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["comp-lzo yes"]))
// XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: ["comp-lzo yes"]))
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["compress"]))
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: base + ["compress lzo"]))
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["compress"]))
XCTAssertNoThrow(try ConfigurationParser.parsed(fromLines: ["compress lzo"]))
}
func testDHCPOption() throws {
let lines = base + [
let lines = [
"dhcp-option DNS 8.8.8.8",
"dhcp-option DNS6 ffff::1",
"dhcp-option DOMAIN example.com",
@ -73,8 +71,18 @@ class ConfigurationParserTests: XCTestCase {
XCTAssertEqual(parsed.proxyBypassDomains, ["foo.com", "bar.org", "net.chat"])
}
func testRedirectGateway() throws {
var parsed: SessionProxy.Configuration
parsed = try! ConfigurationParser.parsed(fromLines: []).configuration
XCTAssertEqual(parsed.routingPolicies, nil)
XCTAssertNotEqual(parsed.routingPolicies, [])
parsed = try! ConfigurationParser.parsed(fromLines: ["redirect-gateway ipv4 block-local"]).configuration
XCTAssertEqual(parsed.routingPolicies, [.IPv4, .IPv6])
}
func testConnectionBlock() throws {
let lines = base + ["<connection>", "</connection>"]
let lines = ["<connection>", "</connection>"]
XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: lines))
}