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 ## Unreleased
### Added
- OpenVPN: Support for `--route-nopull`. [#280](https://github.com/passepartoutvpn/tunnelkit/pull/280)
### Changed ### Changed
- Upgrade OpenSSL to 1.1.1q. - Upgrade OpenSSL to 1.1.1q.
### Fixed
- OpenVPN: Prioritize server configuration over client (standard behavior).
## 5.0.0 (2022-09-23) ## 5.0.0 (2022-09-23)
### Added ### 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) { 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 isIPv4Gateway = routingPolicies?.contains(.IPv4) ?? false
let isIPv6Gateway = routingPolicies?.contains(.IPv6) ?? false let isIPv6Gateway = routingPolicies?.contains(.IPv6) ?? false
let isGateway = isIPv4Gateway || isIPv6Gateway let isGateway = isIPv4Gateway || isIPv6Gateway
@ -585,12 +590,14 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway.maskedDescription)") log.info("Routing.IPv4: Setting default gateway to \(ipv4.defaultGateway.maskedDescription)")
} }
if pullRoutes {
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 ipv4Route.gatewayAddress = r.gateway
routes.append(ipv4Route) routes.append(ipv4Route)
log.info("Routing.IPv4: Adding route \(r.destination.maskedDescription)/\(r.mask) -> \(r.gateway)") log.info("Routing.IPv4: Adding route \(r.destination.maskedDescription)/\(r.mask) -> \(r.gateway)")
} }
}
ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask]) ipv4Settings = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask])
ipv4Settings?.includedRoutes = routes ipv4Settings?.includedRoutes = routes
@ -614,12 +621,14 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway.maskedDescription)") log.info("Routing.IPv6: Setting default gateway to \(ipv6.defaultGateway.maskedDescription)")
} }
if pullRoutes {
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 ipv6Route.gatewayAddress = r.gateway
routes.append(ipv6Route) routes.append(ipv6Route)
log.info("Routing.IPv6: Adding route \(r.destination.maskedDescription)/\(r.prefixLength) -> \(r.gateway)") log.info("Routing.IPv6: Adding route \(r.destination.maskedDescription)/\(r.prefixLength) -> \(r.gateway)")
} }
}
ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber]) ipv6Settings = NEIPv6Settings(addresses: [ipv6.address], networkPrefixLengths: [ipv6.addressPrefixLength as NSNumber])
ipv6Settings?.includedRoutes = routes ipv6Settings?.includedRoutes = routes
@ -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 // fall back
if dnsSettings == nil { if dnsSettings == nil {
dnsServers = [] dnsServers = (pullDNS ? options.dnsServers : localOptions.dnsServers) ?? []
if let servers = localOptions.dnsServers,
!servers.isEmpty {
dnsServers = servers
} else if let servers = options.dnsServers {
dnsServers = servers
}
if !dnsServers.isEmpty { if !dnsServers.isEmpty {
log.info("DNS: Using servers \(dnsServers.maskedDescription)") log.info("DNS: Using servers \(dnsServers.maskedDescription)")
dnsSettings = NEDNSSettings(servers: dnsServers) dnsSettings = NEDNSSettings(servers: dnsServers)
@ -695,7 +718,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
dnsSettings?.matchDomains = [""] 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)") log.info("DNS: Using search domains \(searchDomains.maskedDescription)")
dnsSettings?.domainName = searchDomains.first dnsSettings?.domainName = searchDomains.first
dnsSettings?.searchDomains = searchDomains dnsSettings?.searchDomains = searchDomains
@ -718,13 +741,13 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
var proxySettings: NEProxySettings? var proxySettings: NEProxySettings?
if localOptions.isProxyEnabled ?? true { if localOptions.isProxyEnabled ?? true {
if let httpsProxy = cfg.configuration.httpsProxy ?? options.httpsProxy { if let httpsProxy = pullProxy ? options.httpsProxy : localOptions.httpsProxy {
proxySettings = NEProxySettings() proxySettings = NEProxySettings()
proxySettings?.httpsServer = httpsProxy.neProxy() proxySettings?.httpsServer = httpsProxy.neProxy()
proxySettings?.httpsEnabled = true proxySettings?.httpsEnabled = true
log.info("Routing: Setting HTTPS proxy \(httpsProxy.address.maskedDescription):\(httpsProxy.port)") 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 { if proxySettings == nil {
proxySettings = NEProxySettings() proxySettings = NEProxySettings()
} }
@ -732,7 +755,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
proxySettings?.httpEnabled = true proxySettings?.httpEnabled = true
log.info("Routing: Setting HTTP proxy \(httpProxy.address.maskedDescription):\(httpProxy.port)") 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 { if proxySettings == nil {
proxySettings = NEProxySettings() proxySettings = NEProxySettings()
} }
@ -742,7 +765,7 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
} }
// only set if there is a proxy (proxySettings set to non-nil above) // 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 proxySettings?.exceptionList = bypass
log.info("Routing: Setting proxy by-pass list: \(bypass.maskedDescription)") log.info("Routing: Setting proxy by-pass list: \(bypass.maskedDescription)")
} }

View File

@ -152,6 +152,19 @@ extension OpenVPN {
case blockLocal 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`. /// The way to create a `Configuration` object for a `OpenVPNSession`.
public struct ConfigurationBuilder { public struct ConfigurationBuilder {
@ -289,6 +302,9 @@ extension OpenVPN {
/// Policies for redirecting traffic through the VPN gateway. /// Policies for redirecting traffic through the VPN gateway.
public var routingPolicies: [RoutingPolicy]? public var routingPolicies: [RoutingPolicy]?
/// Server settings that must not be pulled.
public var noPullMask: [PullMask]?
/** /**
Creates a `ConfigurationBuilder`. Creates a `ConfigurationBuilder`.
@ -347,7 +363,8 @@ extension OpenVPN {
httpsProxy: httpsProxy, httpsProxy: httpsProxy,
proxyAutoConfigurationURL: proxyAutoConfigurationURL, proxyAutoConfigurationURL: proxyAutoConfigurationURL,
proxyBypassDomains: proxyBypassDomains, proxyBypassDomains: proxyBypassDomains,
routingPolicies: routingPolicies routingPolicies: routingPolicies,
noPullMask: noPullMask
) )
} }
} }
@ -478,6 +495,9 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.routingPolicies` /// - Seealso: `ConfigurationBuilder.routingPolicies`
public let routingPolicies: [RoutingPolicy]? public let routingPolicies: [RoutingPolicy]?
/// - Seealso: `ConfigurationBuilder.noPullMask`
public let noPullMask: [PullMask]?
// MARK: Shortcuts // MARK: Shortcuts
public var fallbackCipher: Cipher { public var fallbackCipher: Cipher {
@ -495,6 +515,15 @@ extension OpenVPN {
public var fallbackCompressionAlgorithm: CompressionAlgorithm { public var fallbackCompressionAlgorithm: CompressionAlgorithm {
return compressionAlgorithm ?? Fallback.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.keepAliveInterval = keepAliveInterval
builder.keepAliveTimeout = keepAliveTimeout builder.keepAliveTimeout = keepAliveTimeout
builder.renegotiatesAfter = renegotiatesAfter builder.renegotiatesAfter = renegotiatesAfter
builder.xorMask = xorMask
builder.remotes = remotes builder.remotes = remotes
builder.checksEKU = checksEKU builder.checksEKU = checksEKU
builder.checksSANHost = checksSANHost builder.checksSANHost = checksSANHost
@ -547,7 +577,7 @@ extension OpenVPN.Configuration {
builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL
builder.proxyBypassDomains = proxyBypassDomains builder.proxyBypassDomains = proxyBypassDomains
builder.routingPolicies = routingPolicies builder.routingPolicies = routingPolicies
builder.xorMask = xorMask builder.noPullMask = noPullMask
return builder return builder
} }
} }
@ -655,5 +685,8 @@ extension OpenVPN.Configuration {
} else { } else {
log.info("\tMTU: default") 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 redirectGateway = NSRegularExpression("^redirect-gateway.*")
static let routeNoPull = NSRegularExpression("^route-nopull")
// MARK: Unsupported // MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+") // static let fragment = NSRegularExpression("^fragment +\\d+")
@ -291,6 +293,7 @@ extension OpenVPN {
var optProxyAutoConfigurationURL: URL? var optProxyAutoConfigurationURL: URL?
var optProxyBypass: [String]? var optProxyBypass: [String]?
var optRedirectGateway: Set<RedirectGateway>? var optRedirectGateway: Set<RedirectGateway>?
var optRouteNoPull: Bool?
log.verbose("Configuration file:") log.verbose("Configuration file:")
for line in lines { for line in lines {
@ -702,6 +705,9 @@ extension OpenVPN {
optRedirectGateway?.insert(opt) optRedirectGateway?.insert(opt)
} }
} }
Regex.routeNoPull.enumerateComponents(in: line) { _ in
optRouteNoPull = true
}
// //
@ -888,6 +894,9 @@ extension OpenVPN {
sessionBuilder.httpsProxy = optHTTPSProxy sessionBuilder.httpsProxy = optHTTPSProxy
sessionBuilder.proxyAutoConfigurationURL = optProxyAutoConfigurationURL sessionBuilder.proxyAutoConfigurationURL = optProxyAutoConfigurationURL
sessionBuilder.proxyBypassDomains = optProxyBypass sessionBuilder.proxyBypassDomains = optProxyBypass
if optRouteNoPull ?? false {
sessionBuilder.noPullMask = [.routes, .dns, .proxy]
}
if let flags = optRedirectGateway { if let flags = optRedirectGateway {
var policies: Set<RoutingPolicy> = [] var policies: Set<RoutingPolicy> = []