Merge branch 'routing-policies'
This commit is contained in:
commit
e17c5d0fdd
|
@ -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)
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
|
|
|
@ -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 = []
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue