Update Kit

- Move NetworkSettingsBuilder to OpenVPN/OpenSSL
- Fix flaky tests
This commit is contained in:
Davide 2025-01-13 14:59:18 +01:00
parent a2720d2dfc
commit 3510f2b153
No known key found for this signature in database
GPG Key ID: A48836171C759F5E
3 changed files with 637 additions and 1 deletions

@ -1 +1 @@
Subproject commit e18a9eb1ae90d9555bd44297e230998afd6613c7
Subproject commit 038af19bd26c08ac36569632ba25c80afcd65840

View File

@ -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
}
}
}

View File

@ -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)
)
}
}