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
|
## 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
|
||||||
|
|
|
@ -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,11 +590,13 @@ 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)")
|
||||||
}
|
}
|
||||||
|
|
||||||
for r in ipv4.routes {
|
if pullRoutes {
|
||||||
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
|
for r in ipv4.routes {
|
||||||
ipv4Route.gatewayAddress = r.gateway
|
let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask)
|
||||||
routes.append(ipv4Route)
|
ipv4Route.gatewayAddress = r.gateway
|
||||||
log.info("Routing.IPv4: Adding route \(r.destination.maskedDescription)/\(r.mask) -> \(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 = NEIPv4Settings(addresses: [ipv4.address], subnetMasks: [ipv4.addressMask])
|
||||||
|
@ -614,11 +621,13 @@ 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)")
|
||||||
}
|
}
|
||||||
|
|
||||||
for r in ipv6.routes {
|
if pullRoutes {
|
||||||
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
|
for r in ipv6.routes {
|
||||||
ipv6Route.gatewayAddress = r.gateway
|
let ipv6Route = NEIPv6Route(destinationAddress: r.destination, networkPrefixLength: r.prefixLength as NSNumber)
|
||||||
routes.append(ipv6Route)
|
ipv6Route.gatewayAddress = r.gateway
|
||||||
log.info("Routing.IPv6: Adding route \(r.destination.maskedDescription)/\(r.prefixLength) -> \(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])
|
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
|
// 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)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> = []
|
||||||
|
|
Loading…
Reference in New Issue