mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-02-08 17:02:04 +00:00
Update Kit
- Move NetworkSettingsBuilder to OpenVPN/OpenSSL - Fix flaky tests
This commit is contained in:
parent
a2720d2dfc
commit
3510f2b153
@ -1 +1 @@
|
|||||||
Subproject commit e18a9eb1ae90d9555bd44297e230998afd6613c7
|
Subproject commit 038af19bd26c08ac36569632ba25c80afcd65840
|
@ -0,0 +1,323 @@
|
|||||||
|
//
|
||||||
|
// NetworkSettingsBuilder.swift
|
||||||
|
// PassepartoutKit
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 3/16/24.
|
||||||
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of PassepartoutKit.
|
||||||
|
//
|
||||||
|
// PassepartoutKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PassepartoutKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
#if !PP_FRAMEWORK
|
||||||
|
import PassepartoutCore
|
||||||
|
import PassepartoutOpenVPN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extension OpenVPN {
|
||||||
|
|
||||||
|
/// Merges local and remote settings.
|
||||||
|
///
|
||||||
|
/// OpenVPN settings may be set locally, but may also received from a remote server. This object merges the local and remote ``OpenVPN/Configuration`` into a digestible list of `Module`.
|
||||||
|
struct NetworkSettingsBuilder {
|
||||||
|
|
||||||
|
/// The client options.
|
||||||
|
let localOptions: Configuration
|
||||||
|
|
||||||
|
/// The server options.
|
||||||
|
let remoteOptions: Configuration
|
||||||
|
|
||||||
|
init(localOptions: Configuration, remoteOptions: Configuration) {
|
||||||
|
self.localOptions = localOptions
|
||||||
|
self.remoteOptions = remoteOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of `Module` mapped from ``localOptions`` and ``remoteOptions``.
|
||||||
|
func modules() -> [Module] {
|
||||||
|
pp_log(.openvpn, .info, "Build modules from local/remote options")
|
||||||
|
|
||||||
|
return [
|
||||||
|
ipModule,
|
||||||
|
dnsModule,
|
||||||
|
httpProxyModule
|
||||||
|
].compactMap { $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func print() {
|
||||||
|
pp_log(.openvpn, .notice, "Negotiated options (remote overrides local)")
|
||||||
|
if let negCipher = remoteOptions.cipher {
|
||||||
|
pp_log(.openvpn, .notice, "\tCipher: \(negCipher.rawValue)")
|
||||||
|
}
|
||||||
|
if let negFraming = remoteOptions.compressionFraming {
|
||||||
|
pp_log(.openvpn, .notice, "\tCompression framing: \(negFraming)")
|
||||||
|
}
|
||||||
|
if let negCompression = remoteOptions.compressionAlgorithm {
|
||||||
|
pp_log(.openvpn, .notice, "\tCompression algorithm: \(negCompression)")
|
||||||
|
}
|
||||||
|
if let negPing = remoteOptions.keepAliveInterval {
|
||||||
|
pp_log(.openvpn, .notice, "\tKeep-alive interval: \(negPing.asTimeString)")
|
||||||
|
}
|
||||||
|
if let negPingRestart = remoteOptions.keepAliveTimeout {
|
||||||
|
pp_log(.openvpn, .notice, "\tKeep-alive timeout: \(negPingRestart.asTimeString)")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Pull
|
||||||
|
|
||||||
|
private extension OpenVPN.NetworkSettingsBuilder {
|
||||||
|
var pullRoutes: Bool {
|
||||||
|
!(localOptions.noPullMask?.contains(.routes) ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pullDNS: Bool {
|
||||||
|
!(localOptions.noPullMask?.contains(.dns) ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pullProxy: Bool {
|
||||||
|
!(localOptions.noPullMask?.contains(.proxy) ?? false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Overall
|
||||||
|
|
||||||
|
private extension OpenVPN.NetworkSettingsBuilder {
|
||||||
|
var isGateway: Bool {
|
||||||
|
isIPv4Gateway || isIPv6Gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
var routingPolicies: [OpenVPN.RoutingPolicy]? {
|
||||||
|
pullRoutes ? (remoteOptions.routingPolicies ?? localOptions.routingPolicies) : localOptions.routingPolicies
|
||||||
|
}
|
||||||
|
|
||||||
|
var isIPv4Gateway: Bool {
|
||||||
|
routingPolicies?.contains(.IPv4) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
var isIPv6Gateway: Bool {
|
||||||
|
routingPolicies?.contains(.IPv6) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
var allRoutes4: [Route] {
|
||||||
|
var routes = localOptions.routes4 ?? []
|
||||||
|
if pullRoutes, let remoteRoutes = remoteOptions.routes4 {
|
||||||
|
routes.append(contentsOf: remoteRoutes)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
var allRoutes6: [Route] {
|
||||||
|
var routes = localOptions.routes6 ?? []
|
||||||
|
if pullRoutes, let remoteRoutes = remoteOptions.routes6 {
|
||||||
|
routes.append(contentsOf: remoteRoutes)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
var allDNSServers: [String] {
|
||||||
|
var servers = localOptions.dnsServers ?? []
|
||||||
|
if pullDNS, let remoteServers = remoteOptions.dnsServers {
|
||||||
|
servers.append(contentsOf: remoteServers)
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsDomain: String? {
|
||||||
|
var domain = localOptions.dnsDomain
|
||||||
|
if pullDNS, let remoteDomain = remoteOptions.dnsDomain {
|
||||||
|
domain = remoteDomain
|
||||||
|
}
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
var allDNSSearchDomains: [String] {
|
||||||
|
var searchDomains = localOptions.searchDomains ?? []
|
||||||
|
if pullDNS, let remoteSearchDomains = remoteOptions.searchDomains {
|
||||||
|
searchDomains.append(contentsOf: remoteSearchDomains)
|
||||||
|
}
|
||||||
|
return searchDomains
|
||||||
|
}
|
||||||
|
|
||||||
|
var allProxyBypassDomains: [String] {
|
||||||
|
var bypass = localOptions.proxyBypassDomains ?? []
|
||||||
|
if pullProxy, let remoteBypass = remoteOptions.proxyBypassDomains {
|
||||||
|
bypass.append(contentsOf: remoteBypass)
|
||||||
|
}
|
||||||
|
return bypass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - IP
|
||||||
|
|
||||||
|
private extension OpenVPN.NetworkSettingsBuilder {
|
||||||
|
|
||||||
|
// IPv4/6 address/mask MUST come from server options
|
||||||
|
// routes, instead, can both come from server and local options
|
||||||
|
|
||||||
|
var ipModule: Module? {
|
||||||
|
let ipv4 = ipv4Settings
|
||||||
|
let ipv6 = ipv6Settings
|
||||||
|
let mtu: Int?
|
||||||
|
if let localMTU = localOptions.mtu, localMTU > 0 {
|
||||||
|
mtu = localMTU
|
||||||
|
} else {
|
||||||
|
mtu = nil
|
||||||
|
}
|
||||||
|
guard ipv4 != nil || ipv6 != nil || mtu != nil else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return IPModule.Builder(
|
||||||
|
ipv4: ipv4,
|
||||||
|
ipv6: ipv6,
|
||||||
|
mtu: mtu
|
||||||
|
).tryBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipv4Settings: IPSettings? {
|
||||||
|
guard let ipv4 = remoteOptions.ipv4 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend main routes
|
||||||
|
var target = allRoutes4
|
||||||
|
target.insert(contentsOf: ipv4.includedRoutes, at: 0)
|
||||||
|
|
||||||
|
let routes: [Route] = target.compactMap { route in
|
||||||
|
let ipv4Route = Route(route.destination, route.gateway)
|
||||||
|
if route.destination == nil {
|
||||||
|
guard isIPv4Gateway, let gw = route.gateway else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pp_log(.openvpn, .info, "\tIPv4: Set default gateway to \(gw)")
|
||||||
|
} else {
|
||||||
|
pp_log(.openvpn, .info, "\tIPv4: Add route \(route.destination?.description ?? "default") -> \(route.gateway?.description ?? "*")")
|
||||||
|
}
|
||||||
|
return ipv4Route
|
||||||
|
}
|
||||||
|
return ipv4.including(routes: routes)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipv6Settings: IPSettings? {
|
||||||
|
guard let ipv6 = remoteOptions.ipv6 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend main routes
|
||||||
|
var target = allRoutes6
|
||||||
|
target.insert(contentsOf: ipv6.includedRoutes, at: 0)
|
||||||
|
|
||||||
|
let routes: [Route] = target.compactMap { route in
|
||||||
|
let ipv6Route = Route(route.destination, route.gateway)
|
||||||
|
if route.destination == nil {
|
||||||
|
guard isIPv6Gateway, let gw = route.gateway else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pp_log(.openvpn, .info, "\tIPv6: Set default gateway to \(gw)")
|
||||||
|
} else {
|
||||||
|
pp_log(.openvpn, .info, "\tIPv6: Add route \(route.destination?.description ?? "default") -> \(route.gateway?.description ?? "*")")
|
||||||
|
}
|
||||||
|
return ipv6Route
|
||||||
|
}
|
||||||
|
return ipv6.including(routes: routes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - DNS
|
||||||
|
|
||||||
|
private extension OpenVPN.NetworkSettingsBuilder {
|
||||||
|
private var dnsModule: Module? {
|
||||||
|
let dnsServers = allDNSServers
|
||||||
|
guard !dnsServers.isEmpty else {
|
||||||
|
if isGateway {
|
||||||
|
pp_log(.openvpn, .error, "DNS: No settings provided")
|
||||||
|
} else {
|
||||||
|
pp_log(.openvpn, .error, "DNS: No settings provided, use system settings")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pp_log(.openvpn, .info, "\tDNS: Set servers \(dnsServers.map(\.asSensitiveAddress))")
|
||||||
|
var dnsSettings = DNSModule.Builder(servers: dnsServers)
|
||||||
|
|
||||||
|
if let domain = dnsDomain {
|
||||||
|
pp_log(.openvpn, .info, "\tDNS: Set domain: \(domain.asSensitiveAddress)")
|
||||||
|
dnsSettings.domainName = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchDomains = allDNSSearchDomains
|
||||||
|
if !searchDomains.isEmpty {
|
||||||
|
pp_log(.openvpn, .info, "\tDNS: Set search domains: \(searchDomains.map(\.asSensitiveAddress))")
|
||||||
|
dnsSettings.searchDomains = searchDomains
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try dnsSettings.tryBuild()
|
||||||
|
} catch {
|
||||||
|
pp_log(.openvpn, .error, "DNS: Unable to build settings: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - HTTP Proxy
|
||||||
|
|
||||||
|
private extension OpenVPN.NetworkSettingsBuilder {
|
||||||
|
private var httpProxyModule: Module? {
|
||||||
|
var proxySettings: HTTPProxyModule.Builder?
|
||||||
|
|
||||||
|
if let httpsProxy = pullProxy ? (remoteOptions.httpsProxy ?? localOptions.httpsProxy) : localOptions.httpsProxy {
|
||||||
|
proxySettings = HTTPProxyModule.Builder()
|
||||||
|
proxySettings?.secureAddress = httpsProxy.address.rawValue
|
||||||
|
proxySettings?.securePort = httpsProxy.port
|
||||||
|
pp_log(.openvpn, .info, "\tHTTPProxy: Set HTTPS proxy \(httpsProxy.asSensitiveAddress)")
|
||||||
|
}
|
||||||
|
if let httpProxy = pullProxy ? (remoteOptions.httpProxy ?? localOptions.httpProxy) : localOptions.httpProxy {
|
||||||
|
if proxySettings == nil {
|
||||||
|
proxySettings = HTTPProxyModule.Builder()
|
||||||
|
}
|
||||||
|
proxySettings?.address = httpProxy.address.rawValue
|
||||||
|
proxySettings?.port = httpProxy.port
|
||||||
|
pp_log(.openvpn, .info, "\tHTTPProxy: Set HTTP proxy \(httpProxy.asSensitiveAddress)")
|
||||||
|
}
|
||||||
|
if let pacURL = pullProxy ? (remoteOptions.proxyAutoConfigurationURL ?? localOptions.proxyAutoConfigurationURL) : localOptions.proxyAutoConfigurationURL {
|
||||||
|
if proxySettings == nil {
|
||||||
|
proxySettings = HTTPProxyModule.Builder()
|
||||||
|
}
|
||||||
|
proxySettings?.pacURLString = pacURL.absoluteString
|
||||||
|
pp_log(.openvpn, .info, "\tHTTPProxy: Set PAC \(pacURL.absoluteString.asSensitiveAddress)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// only set if there is a proxy (proxySettings set to non-nil above)
|
||||||
|
if proxySettings != nil {
|
||||||
|
let bypass = allProxyBypassDomains
|
||||||
|
if !bypass.isEmpty {
|
||||||
|
proxySettings?.bypassDomains = bypass
|
||||||
|
pp_log(.openvpn, .info, "\tHTTPProxy: Set by-pass list: \(bypass.map(\.asSensitiveAddress))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try proxySettings?.tryBuild()
|
||||||
|
} catch {
|
||||||
|
pp_log(.openvpn, .error, "HTTPProxy: Unable to build settings: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,313 @@
|
|||||||
|
//
|
||||||
|
// NetworkSettingsBuilderTests.swift
|
||||||
|
// PassepartoutKit
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 4/12/24.
|
||||||
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of PassepartoutKit.
|
||||||
|
//
|
||||||
|
// PassepartoutKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// PassepartoutKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
@testable import PassepartoutOpenVPNOpenSSL
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class NetworkSettingsBuilderTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: IP
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildIPModule_thenRequiresRemoteIP() throws {
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
|
||||||
|
remoteOptions.ipv4 = nil
|
||||||
|
remoteOptions.ipv6 = nil
|
||||||
|
XCTAssertNil(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
remoteOptions.ipv4 = IPSettings(subnet: Subnet(rawValue: "100.1.2.3/32")!)
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
|
||||||
|
remoteOptions.ipv4 = nil
|
||||||
|
remoteOptions.ipv6 = nil
|
||||||
|
XCTAssertNil(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
remoteOptions.ipv6 = IPSettings(subnet: Subnet(rawValue: "100:1:2::3/32")!)
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildIPModule_thenMergesRoutes() throws {
|
||||||
|
var sut: IPModule
|
||||||
|
let allRoutes4 = [
|
||||||
|
Route(Subnet(rawValue: "1.1.1.1/16")!, nil),
|
||||||
|
Route(Subnet(rawValue: "2.2.2.2/8")!, nil),
|
||||||
|
Route(Subnet(rawValue: "3.3.3.3/24")!, nil),
|
||||||
|
Route(Subnet(rawValue: "4.4.4.4/32")!, nil)
|
||||||
|
]
|
||||||
|
let allRoutes6 = [
|
||||||
|
Route(Subnet(rawValue: "::1/16")!, nil),
|
||||||
|
Route(Subnet(rawValue: "::2/8")!, nil),
|
||||||
|
Route(Subnet(rawValue: "::3/24")!, nil),
|
||||||
|
Route(Subnet(rawValue: "::4/32")!, nil)
|
||||||
|
]
|
||||||
|
let localRoutes4 = Array(allRoutes4.prefix(2))
|
||||||
|
let localRoutes6 = Array(allRoutes6.prefix(2))
|
||||||
|
let remoteRoutes4 = Array(allRoutes4.suffix(from: 2))
|
||||||
|
let remoteRoutes6 = Array(allRoutes6.suffix(from: 2))
|
||||||
|
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
localOptions.routes4 = localRoutes4
|
||||||
|
localOptions.routes6 = localRoutes6
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
remoteOptions.ipv4 = IPSettings(subnet: Subnet(rawValue: "100.1.2.3/32")!)
|
||||||
|
remoteOptions.ipv6 = IPSettings(subnet: Subnet(rawValue: "100:1:2::3/32")!)
|
||||||
|
remoteOptions.routes4 = remoteRoutes4
|
||||||
|
remoteOptions.routes6 = remoteRoutes6
|
||||||
|
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.ipv4?.includedRoutes, allRoutes4)
|
||||||
|
XCTAssertEqual(sut.ipv6?.includedRoutes, allRoutes6)
|
||||||
|
|
||||||
|
localOptions.noPullMask = [.routes]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.ipv4?.includedRoutes, localOptions.routes4)
|
||||||
|
XCTAssertEqual(sut.ipv6?.includedRoutes, localOptions.routes6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildIPModule_thenFollowsRoutingPolicies() throws {
|
||||||
|
var sut: IPModule
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
remoteOptions.ipv4 = IPSettings(
|
||||||
|
subnet: Subnet(try XCTUnwrap(Address(rawValue: "1.1.1.1")), 16)
|
||||||
|
)
|
||||||
|
.including(
|
||||||
|
routes: [
|
||||||
|
Route(defaultWithGateway: try XCTUnwrap(Address(rawValue: "6.6.6.6")))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
remoteOptions.ipv6 = IPSettings(
|
||||||
|
subnet: Subnet(try XCTUnwrap(Address(rawValue: "1:1::1")), 72)
|
||||||
|
)
|
||||||
|
.including(
|
||||||
|
routes: [
|
||||||
|
Route(defaultWithGateway: try XCTUnwrap(Address(rawValue: "::6")))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
XCTAssertEqual(sut.ipv4?.subnet?.rawValue, "1.1.1.1/16")
|
||||||
|
XCTAssertEqual(sut.ipv6?.subnet?.rawValue, "1:1::1/72")
|
||||||
|
XCTAssertNil(sut.ipv4?.defaultGateway?.rawValue)
|
||||||
|
XCTAssertNil(sut.ipv6?.defaultGateway?.rawValue)
|
||||||
|
|
||||||
|
remoteOptions.routingPolicies = [.IPv4]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
XCTAssertEqual(sut.ipv4?.defaultGateway?.rawValue, "6.6.6.6")
|
||||||
|
XCTAssertNil(sut.ipv6?.defaultGateway?.rawValue)
|
||||||
|
|
||||||
|
remoteOptions.routingPolicies = [.IPv6]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
XCTAssertNil(sut.ipv4?.defaultGateway?.rawValue)
|
||||||
|
XCTAssertEqual(sut.ipv6?.defaultGateway?.rawValue, "::6")
|
||||||
|
|
||||||
|
remoteOptions.routingPolicies = [.IPv4, .IPv6]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: IPModule.self, with: remoteOptions))
|
||||||
|
XCTAssertEqual(sut.ipv4?.defaultGateway?.rawValue, "6.6.6.6")
|
||||||
|
XCTAssertEqual(sut.ipv6?.defaultGateway?.rawValue, "::6")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: DNS
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildDNSModule_thenRequiresServers() throws {
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
|
||||||
|
XCTAssertNil(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.dnsServers = ["1.1.1.1"]
|
||||||
|
remoteOptions.dnsServers = nil
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.dnsServers = nil
|
||||||
|
remoteOptions.dnsServers = ["1.1.1.1"]
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.dnsServers = []
|
||||||
|
remoteOptions.dnsServers = []
|
||||||
|
XCTAssertNil(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildDNSModule_thenMergesServers() throws {
|
||||||
|
var sut: DNSModule
|
||||||
|
let allServers = [
|
||||||
|
Address(rawValue: "1.1.1.1")!,
|
||||||
|
Address(rawValue: "2.2.2.2")!,
|
||||||
|
Address(rawValue: "3.3.3.3")!
|
||||||
|
]
|
||||||
|
let localServers = Array(allServers.prefix(2))
|
||||||
|
let remoteServers = Array(allServers.suffix(from: 2))
|
||||||
|
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
localOptions.dnsServers = localServers.map(\.rawValue)
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
remoteOptions.dnsServers = remoteServers.map(\.rawValue)
|
||||||
|
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.servers, allServers)
|
||||||
|
|
||||||
|
localOptions.noPullMask = [.dns]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.servers, localServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildDNSModule_thenMergesDomains() throws {
|
||||||
|
var sut: DNSModule
|
||||||
|
let allDomains = [
|
||||||
|
Address(rawValue: "one.com")!,
|
||||||
|
Address(rawValue: "two.com")!,
|
||||||
|
Address(rawValue: "three.com")!
|
||||||
|
]
|
||||||
|
let localDomains = Array(allDomains.prefix(2))
|
||||||
|
let remoteDomains = Array(allDomains.suffix(from: 2))
|
||||||
|
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
localOptions.dnsServers = ["1.1.1.1"]
|
||||||
|
localOptions.searchDomains = localDomains.map(\.rawValue)
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
remoteOptions.searchDomains = remoteDomains.map(\.rawValue)
|
||||||
|
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.searchDomains, allDomains)
|
||||||
|
|
||||||
|
localOptions.noPullMask = [.dns]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: DNSModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.searchDomains, localDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Proxy
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildHTTPProxyModule_thenRequiresEndpoint() throws {
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
|
||||||
|
XCTAssertNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.httpProxy = Endpoint(rawValue: "1.1.1.1:8080")!
|
||||||
|
remoteOptions.httpProxy = nil
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
localOptions.httpsProxy = Endpoint(rawValue: "1.1.1.1:8080")!
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.httpProxy = nil
|
||||||
|
remoteOptions.httpProxy = Endpoint(rawValue: "1.1.1.1:8080")!
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
remoteOptions.httpsProxy = Endpoint(rawValue: "1.1.1.1:8080")!
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.httpProxy = nil
|
||||||
|
remoteOptions.httpProxy = nil
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
localOptions.httpsProxy = nil
|
||||||
|
remoteOptions.httpsProxy = nil
|
||||||
|
XCTAssertNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildACProxyModule_thenRequiresURL() throws {
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
|
||||||
|
XCTAssertNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.proxyAutoConfigurationURL = URL(string: "https://www.gogle.com")!
|
||||||
|
remoteOptions.proxyAutoConfigurationURL = nil
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.proxyAutoConfigurationURL = nil
|
||||||
|
remoteOptions.proxyAutoConfigurationURL = URL(string: "https://www.gogle.com")!
|
||||||
|
XCTAssertNotNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
|
||||||
|
localOptions.proxyAutoConfigurationURL = nil
|
||||||
|
remoteOptions.proxyAutoConfigurationURL = nil
|
||||||
|
XCTAssertNil(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildProxyModule_thenMergesBypassDomains() throws {
|
||||||
|
var sut: HTTPProxyModule
|
||||||
|
let allDomains = [
|
||||||
|
Address(rawValue: "one.com")!,
|
||||||
|
Address(rawValue: "two.com")!,
|
||||||
|
Address(rawValue: "three.com")!
|
||||||
|
]
|
||||||
|
let localDomains = Array(allDomains.prefix(2))
|
||||||
|
let remoteDomains = Array(allDomains.suffix(from: 2))
|
||||||
|
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
localOptions.httpProxy = Endpoint(rawValue: "1.1.1.1:8080")!
|
||||||
|
localOptions.proxyBypassDomains = localDomains.map(\.rawValue)
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
remoteOptions.proxyBypassDomains = remoteDomains.map(\.rawValue)
|
||||||
|
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.bypassDomains, allDomains)
|
||||||
|
|
||||||
|
localOptions.noPullMask = [.proxy]
|
||||||
|
sut = try XCTUnwrap(try builtModule(ofType: HTTPProxyModule.self, with: remoteOptions, localOptions: localOptions))
|
||||||
|
XCTAssertEqual(sut.bypassDomains, localDomains)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: MTU
|
||||||
|
|
||||||
|
func test_givenSettings_whenBuildMTU_thenReturnsLocalMTU() throws {
|
||||||
|
var sut: OpenVPN.NetworkSettingsBuilder
|
||||||
|
var localOptions = OpenVPN.Configuration.Builder()
|
||||||
|
var remoteOptions = OpenVPN.Configuration.Builder()
|
||||||
|
|
||||||
|
localOptions.mtu = 1200
|
||||||
|
sut = try newBuilder(with: remoteOptions, localOptions: localOptions)
|
||||||
|
XCTAssertEqual((sut.modules().first as? IPModule)?.mtu, localOptions.mtu)
|
||||||
|
|
||||||
|
remoteOptions.mtu = 1400
|
||||||
|
sut = try newBuilder(with: remoteOptions, localOptions: localOptions)
|
||||||
|
XCTAssertEqual((sut.modules().first as? IPModule)?.mtu, localOptions.mtu)
|
||||||
|
|
||||||
|
localOptions.mtu = nil
|
||||||
|
sut = try newBuilder(with: remoteOptions, localOptions: localOptions)
|
||||||
|
XCTAssertNil((sut.modules().first as? IPModule)?.mtu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension NetworkSettingsBuilderTests {
|
||||||
|
func builtModule<T>(
|
||||||
|
ofType type: T.Type,
|
||||||
|
with remoteOptions: OpenVPN.Configuration.Builder,
|
||||||
|
localOptions: OpenVPN.Configuration.Builder? = nil
|
||||||
|
) throws -> T? where T: Module {
|
||||||
|
try newBuilder(with: remoteOptions, localOptions: localOptions)
|
||||||
|
.modules()
|
||||||
|
.first(ofType: type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuilder(
|
||||||
|
with remoteOptions: OpenVPN.Configuration.Builder,
|
||||||
|
localOptions: OpenVPN.Configuration.Builder? = nil
|
||||||
|
) throws -> OpenVPN.NetworkSettingsBuilder {
|
||||||
|
OpenVPN.NetworkSettingsBuilder(
|
||||||
|
localOptions: try (localOptions ?? OpenVPN.Configuration.Builder()).tryBuild(isClient: false),
|
||||||
|
remoteOptions: try remoteOptions.tryBuild(isClient: false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user