Support --route-nopull (#280)

* Parse --route-no-pull

When provided, pull everything except:

- Routes
- DNS
- Proxy

Implement with higher granularity compared to OpenVPN.

* Apply no-pull mask in tunnel settings

Pull server settings by default to match standard OpenVPN
behavior. Library was prioritizing client over server.

* Add link in CHANGELOG
This commit is contained in:
Davide De Rosa 2022-10-08 08:54:55 +02:00 committed by GitHub
parent 9f5de0fc55
commit a3822678cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 26 deletions

View File

@ -7,10 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- OpenVPN: Support for `--route-nopull`. [#280](https://github.com/passepartoutvpn/tunnelkit/pull/280)
### Changed
- Upgrade OpenSSL to 1.1.1q.
### Fixed
- OpenVPN: Prioritize server configuration over client (standard behavior).
## 5.0.0 (2022-09-23)
### Added

View File

@ -563,7 +563,12 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
}
private func bringNetworkUp(remoteAddress: String, localOptions: OpenVPN.Configuration, options: OpenVPN.Configuration, completionHandler: @escaping (Error?) -> Void) {
let routingPolicies = localOptions.routingPolicies ?? options.routingPolicies
let pullMask = localOptions.pullMask
let pullRoutes = pullMask?.contains(.routes) ?? false
let pullDNS = pullMask?.contains(.dns) ?? false
let pullProxy = pullMask?.contains(.proxy) ?? false
let routingPolicies = pullRoutes ? options.routingPolicies : localOptions.routingPolicies
let isIPv4Gateway = routingPolicies?.contains(.IPv4) ?? false
let isIPv6Gateway = routingPolicies?.contains(.IPv6) ?? false
let isGateway = isIPv4Gateway || isIPv6Gateway
@ -585,13 +590,15 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway.maskedDescription)")
}
for r in ipv4.routes {
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
ipv4Route.gatewayAddress = r.gateway
routes.append(ipv4Route)
log.info("Routing.IPv4: Adding route \(r.destination.maskedDescription)/\(r.mask) -> \(r.gateway)")
if pullRoutes {
for r in ipv4.routes {
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
ipv4Route.gatewayAddress = r.gateway
routes.append(ipv4Route)
log.info("Routing.IPv4: Adding route \(r.destination.maskedDescription)/\(r.mask) -> \(r.gateway)")
}
}
ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask])
ipv4Settings?.includedRoutes = routes
ipv4Settings?.excludedRoutes = []
@ -614,11 +621,13 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway.maskedDescription)")
}
for r in ipv6.routes {
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
ipv6Route.gatewayAddress = r.gateway
routes.append(ipv6Route)
log.info("Routing.IPv6: Adding route \(r.destination.maskedDescription)/\(r.prefixLength) -> \(r.gateway)")
if pullRoutes {
for r in ipv6.routes {
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
ipv6Route.gatewayAddress = r.gateway
routes.append(ipv6Route)
log.info("Routing.IPv6: Adding route \(r.destination.maskedDescription)/\(r.prefixLength) -> \(r.gateway)")
}
}
ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber])
@ -671,15 +680,29 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
}
}
// ensure that non-nil arrays also imply non-empty
if let array = options.dnsServers {
precondition(!array.isEmpty)
}
if let array = options.searchDomains {
precondition(!array.isEmpty)
}
if let array = options.proxyBypassDomains {
precondition(!array.isEmpty)
}
if let array = cfg.configuration.dnsServers {
precondition(!array.isEmpty)
}
if let array = cfg.configuration.searchDomains {
precondition(!array.isEmpty)
}
if let array = cfg.configuration.proxyBypassDomains {
precondition(!array.isEmpty)
}
// fall back
if dnsSettings == nil {
dnsServers = []
if let servers = localOptions.dnsServers,
!servers.isEmpty {
dnsServers = servers
} else if let servers = options.dnsServers {
dnsServers = servers
}
dnsServers = (pullDNS ? options.dnsServers : localOptions.dnsServers) ?? []
if !dnsServers.isEmpty {
log.info("DNS: Using servers \(dnsServers.maskedDescription)")
dnsSettings = NEDNSSettings(servers: dnsServers)
@ -695,7 +718,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
dnsSettings?.matchDomains = [""]
}
if let searchDomains = localOptions.searchDomains ?? options.searchDomains {
if let searchDomains = pullDNS ? options.searchDomains : localOptions.searchDomains {
log.info("DNS: Using search domains \(searchDomains.maskedDescription)")
dnsSettings?.domainName = searchDomains.first
dnsSettings?.searchDomains = searchDomains
@ -718,13 +741,13 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
var proxySettings: NEProxySettings?
if localOptions.isProxyEnabled ?? true {
if let httpsProxy = cfg.configuration.httpsProxy ?? options.httpsProxy {
if let httpsProxy = pullProxy ? options.httpsProxy : localOptions.httpsProxy {
proxySettings = NEProxySettings()
proxySettings?.httpsServer = httpsProxy.neProxy()
proxySettings?.httpsEnabled = true
log.info("Routing: Setting HTTPS proxy \(httpsProxy.address.maskedDescription):\(httpsProxy.port)")
}
if let httpProxy = localOptions.httpProxy ?? options.httpProxy {
if let httpProxy = pullProxy ? options.httpProxy : localOptions.httpProxy {
if proxySettings == nil {
proxySettings = NEProxySettings()
}
@ -732,7 +755,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
proxySettings?.httpEnabled = true
log.info("Routing: Setting HTTP proxy \(httpProxy.address.maskedDescription):\(httpProxy.port)")
}
if let pacURL = localOptions.proxyAutoConfigurationURL ?? options.proxyAutoConfigurationURL {
if let pacURL = pullProxy ? options.proxyAutoConfigurationURL : localOptions.proxyAutoConfigurationURL {
if proxySettings == nil {
proxySettings = NEProxySettings()
}
@ -742,7 +765,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
}
// only set if there is a proxy (proxySettings set to non-nil above)
if let bypass = localOptions.proxyBypassDomains ?? options.proxyBypassDomains {
if let bypass = pullProxy ? options.proxyBypassDomains : localOptions.proxyBypassDomains {
proxySettings?.exceptionList = bypass
log.info("Routing: Setting proxy by-pass list: \(bypass.maskedDescription)")
}

View File

@ -152,6 +152,19 @@ extension OpenVPN {
case blockLocal
}
/// Settings that can be pulled from server.
public enum PullMask: String, Codable, CaseIterable {
/// Routes and gateways.
case routes
/// DNS settings.
case dns
/// Proxy settings.
case proxy
}
/// The way to create a `Configuration` object for a `OpenVPNSession`.
public struct ConfigurationBuilder {
@ -289,6 +302,9 @@ extension OpenVPN {
/// Policies for redirecting traffic through the VPN gateway.
public var routingPolicies: [RoutingPolicy]?
/// Server settings that must not be pulled.
public var noPullMask: [PullMask]?
/**
Creates a `ConfigurationBuilder`.
@ -347,7 +363,8 @@ extension OpenVPN {
httpsProxy: httpsProxy,
proxyAutoConfigurationURL: proxyAutoConfigurationURL,
proxyBypassDomains: proxyBypassDomains,
routingPolicies: routingPolicies
routingPolicies: routingPolicies,
noPullMask: noPullMask
)
}
}
@ -478,6 +495,9 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.routingPolicies`
public let routingPolicies: [RoutingPolicy]?
/// - Seealso: `ConfigurationBuilder.noPullMask`
public let noPullMask: [PullMask]?
// MARK: Shortcuts
public var fallbackCipher: Cipher {
@ -495,6 +515,15 @@ extension OpenVPN {
public var fallbackCompressionAlgorithm: CompressionAlgorithm {
return compressionAlgorithm ?? Fallback.compressionAlgorithm
}
public var pullMask: [PullMask]? {
let all = PullMask.allCases
guard let notPulled = noPullMask else {
return all
}
let pulled = Array(Set(all).subtracting(notPulled))
return !pulled.isEmpty ? pulled : nil
}
}
}
@ -523,6 +552,7 @@ extension OpenVPN.Configuration {
builder.keepAliveInterval = keepAliveInterval
builder.keepAliveTimeout = keepAliveTimeout
builder.renegotiatesAfter = renegotiatesAfter
builder.xorMask = xorMask
builder.remotes = remotes
builder.checksEKU = checksEKU
builder.checksSANHost = checksSANHost
@ -547,7 +577,7 @@ extension OpenVPN.Configuration {
builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL
builder.proxyBypassDomains = proxyBypassDomains
builder.routingPolicies = routingPolicies
builder.xorMask = xorMask
builder.noPullMask = noPullMask
return builder
}
}
@ -655,5 +685,8 @@ extension OpenVPN.Configuration {
} else {
log.info("\tMTU: default")
}
if let noPullMask = noPullMask {
log.info("\tNot pulled: \(noPullMask.map(\.rawValue))")
}
}
}

View File

@ -119,6 +119,8 @@ extension OpenVPN {
static let redirectGateway = NSRegularExpression("^redirect-gateway.*")
static let routeNoPull = NSRegularExpression("^route-nopull")
// MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+")
@ -291,6 +293,7 @@ extension OpenVPN {
var optProxyAutoConfigurationURL: URL?
var optProxyBypass: [String]?
var optRedirectGateway: Set<RedirectGateway>?
var optRouteNoPull: Bool?
log.verbose("Configuration file:")
for line in lines {
@ -702,6 +705,9 @@ extension OpenVPN {
optRedirectGateway?.insert(opt)
}
}
Regex.routeNoPull.enumerateComponents(in: line) { _ in
optRouteNoPull = true
}
//
@ -888,6 +894,9 @@ extension OpenVPN {
sessionBuilder.httpsProxy = optHTTPSProxy
sessionBuilder.proxyAutoConfigurationURL = optProxyAutoConfigurationURL
sessionBuilder.proxyBypassDomains = optProxyBypass
if optRouteNoPull ?? false {
sessionBuilder.noPullMask = [.routes, .dns, .proxy]
}
if let flags = optRedirectGateway {
var policies: Set<RoutingPolicy> = []