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:
parent
9f5de0fc55
commit
a3822678cf
|
@ -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
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
|
|
|
@ -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))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> = []
|
||||
|
|
Loading…
Reference in New Issue