From 8d5f961114533e527bb9009e1a8cceceb508cc5a Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 12:28:40 +0200 Subject: [PATCH 1/8] Repurpose compound domains with match/search policy --- Sources/PartoutCore/Modules/DNSModule.swift | 64 +++++++++++++-------- Tests/PartoutCoreTests/DNSModuleTests.swift | 18 ++++++ scripts/openapi.yaml | 2 + 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/Sources/PartoutCore/Modules/DNSModule.swift b/Sources/PartoutCore/Modules/DNSModule.swift index 904a3868..64968266 100644 --- a/Sources/PartoutCore/Modules/DNSModule.swift +++ b/Sources/PartoutCore/Modules/DNSModule.swift @@ -12,6 +12,11 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable { case tls(hostname: String) } + public enum DomainPolicy: Hashable, Codable, Sendable { + case search + case match + } + public static let moduleType = ModuleType("DNS") public let id: UniqueID @@ -24,6 +29,8 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable { public let searchDomains: [Address]? + public let domainPolicy: DomainPolicy? + public let routesThroughVPN: Bool? fileprivate init( @@ -32,6 +39,7 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable { servers: [Address], domainName: Address?, searchDomains: [Address]?, + domainPolicy: DomainPolicy?, routesThroughVPN: Bool? ) { self.id = id @@ -39,6 +47,7 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable { self.servers = servers self.domainName = domainName self.searchDomains = searchDomains + self.domainPolicy = domainPolicy self.routesThroughVPN = routesThroughVPN } @@ -50,17 +59,29 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable { switch protocolType { case .cleartext: break - case .https(let url): builder.protocolType = .https builder.dohURL = url.absoluteString - case .tls(let hostname): builder.protocolType = .tls builder.dotHostname = hostname } - builder.domainName = domainName?.rawValue - builder.searchDomains = searchDomains?.map(\.rawValue) + if let domainName { + builder.isFirstDomainPrimary = true + if let searchDomains { + if domainName == searchDomains.first { + builder.domains = searchDomains.map(\.rawValue) + } else { + builder.domains = [domainName.rawValue] + searchDomains.map(\.rawValue) + } + } else { + builder.domains = [domainName.rawValue] + } + } else if let searchDomains { + builder.isFirstDomainPrimary = false + builder.domains = searchDomains.map(\.rawValue) + } + builder.domainPolicy = domainPolicy builder.routesThroughVPN = routesThroughVPN return builder } @@ -78,9 +99,11 @@ extension DNSModule { public var dotHostname: String - public var domainName: String? + public var domains: [String]? + + public var domainPolicy: DomainPolicy? - public var searchDomains: [String]? + public var isFirstDomainPrimary: Bool public var routesThroughVPN: Bool? @@ -94,8 +117,9 @@ extension DNSModule { servers: [String] = [], dohURL: String = "", dotHostname: String = "", - domainName: String? = nil, - searchDomains: [String]? = nil, + domains: [String]? = nil, + domainPolicy: DomainPolicy? = nil, + isFirstDomainPrimary: Bool = true, routesThroughVPN: Bool? = nil ) { self.id = id @@ -103,8 +127,9 @@ extension DNSModule { self.servers = servers self.dohURL = dohURL self.dotHostname = dotHostname - self.domainName = domainName - self.searchDomains = searchDomains + self.domains = domains + self.domainPolicy = domainPolicy + self.isFirstDomainPrimary = isFirstDomainPrimary self.routesThroughVPN = routesThroughVPN } @@ -118,25 +143,15 @@ extension DNSModule { } return addr } - let validDomainName = try domainName.flatMap { + let validDomains = try domains?.compactMap { guard !$0.isEmpty else { return nil as Address? } guard let addr = Address(rawValue: $0), !addr.isIPAddress else { - throw PartoutError.invalidFields(["domainName": $0]) + throw PartoutError.invalidFields(["domains": $0]) } return addr } - let validSearchDomains = try searchDomains?.compactMap { - guard !$0.isEmpty else { - return nil as Address? - } - guard let addr = Address(rawValue: $0), !addr.isIPAddress else { - throw PartoutError.invalidFields(["searchDomains": $0]) - } - return addr - } - let validProtocolType: ProtocolType switch protocolType { case .cleartext: @@ -158,8 +173,9 @@ extension DNSModule { id: id, protocolType: validProtocolType, servers: validServers, - domainName: validDomainName, - searchDomains: validSearchDomains, + domainName: isFirstDomainPrimary ? validDomains?.first : nil, + searchDomains: validDomains, + domainPolicy: domainPolicy, routesThroughVPN: routesThroughVPN ) } diff --git a/Tests/PartoutCoreTests/DNSModuleTests.swift b/Tests/PartoutCoreTests/DNSModuleTests.swift index 77cf1fbf..ec7adf22 100644 --- a/Tests/PartoutCoreTests/DNSModuleTests.swift +++ b/Tests/PartoutCoreTests/DNSModuleTests.swift @@ -39,6 +39,24 @@ struct DNSModuleTests { #expect(sut == module.builder()) } + @Test(arguments: [true, false]) + func givenDomains_whenRebuild_thenIsRestored(isFirstDomainPrimary: Bool) throws { + let sut = DNSModule.Builder( + protocolType: .cleartext, + servers: ["1.2.3.4"], + domains: ["primary.example.com", "search.example.com"], + isFirstDomainPrimary: isFirstDomainPrimary + ) + let module = try sut.build() + let rebuilt = module.builder() + + #expect(module.domainName?.rawValue == (isFirstDomainPrimary ? "primary.example.com" : nil)) + #expect(module.searchDomains?.map(\.rawValue) == ["primary.example.com", "search.example.com"]) + #expect(rebuilt.domains == sut.domains) + #expect(rebuilt.isFirstDomainPrimary == sut.isFirstDomainPrimary) + #expect(rebuilt == sut) + } + @Test func givenHTTPSWithoutURL_whenBuild_thenFails() { let sut = DNSModule.Builder( diff --git a/scripts/openapi.yaml b/scripts/openapi.yaml index edea8d14..3d56b8a0 100644 --- a/scripts/openapi.yaml +++ b/scripts/openapi.yaml @@ -17,6 +17,8 @@ components: properties: domainName: "$ref": "#/components/schemas/Address" + domainPolicy: + title: DomainPolicy id: "$ref": "#/components/schemas/UniqueID" protocolType: From 7338553da16d66095b8248c5e091e9f079dcea8f Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 12:44:31 +0200 Subject: [PATCH 2/8] Update OpenVPN/WireGuard builders --- .../Internal/NetworkSettingsBuilder.swift | 14 ++++++++++++-- .../Internal/Configuration+WgQuickConfig.swift | 2 +- .../StandardWireGuardParser+Validate.swift | 2 +- .../Mappers/LocalInterface+WireGuardKit.swift | 2 +- .../WireGuardParserTests.swift | 6 +++--- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Sources/PartoutOpenVPNConnection/Internal/NetworkSettingsBuilder.swift b/Sources/PartoutOpenVPNConnection/Internal/NetworkSettingsBuilder.swift index 45fa6e93..19531cdb 100644 --- a/Sources/PartoutOpenVPNConnection/Internal/NetworkSettingsBuilder.swift +++ b/Sources/PartoutOpenVPNConnection/Internal/NetworkSettingsBuilder.swift @@ -230,13 +230,23 @@ private extension NetworkSettingsBuilder { if let domain = dnsDomain { pp_log(ctx, .openvpn, .info, "\tDNS: Set domain: \(domain.asSensitiveAddress(ctx))") - dnsSettings.domainName = domain + dnsSettings.domains = [domain] + dnsSettings.isFirstDomainPrimary = true + } else { + dnsSettings.isFirstDomainPrimary = false } let searchDomains = allDNSSearchDomains if !searchDomains.isEmpty { pp_log(ctx, .openvpn, .info, "\tDNS: Set search domains: \(searchDomains.map { $0.asSensitiveAddress(ctx) })") - dnsSettings.searchDomains = searchDomains + // First domain is main domain + if var domains = dnsSettings.domains { + let otherDomains = searchDomains.filter { !domains.contains($0) } + domains.append(contentsOf: otherDomains) + dnsSettings.domains = domains + } else { + dnsSettings.domains = searchDomains + } } do { diff --git a/Sources/PartoutWireGuard/Internal/Configuration+WgQuickConfig.swift b/Sources/PartoutWireGuard/Internal/Configuration+WgQuickConfig.swift index 30fa6e50..2d75b2d8 100644 --- a/Sources/PartoutWireGuard/Internal/Configuration+WgQuickConfig.swift +++ b/Sources/PartoutWireGuard/Internal/Configuration+WgQuickConfig.swift @@ -176,7 +176,7 @@ extension WireGuard.Configuration { } } interface.dns.servers = dnsServers.map(\.rawValue) - interface.dns.searchDomains = dnsSearch + interface.dns.domains = dnsSearch } if let mtuString = attributes["mtu"] { guard let mtu = UInt16(mtuString) else { diff --git a/Sources/PartoutWireGuard/StandardWireGuardParser+Validate.swift b/Sources/PartoutWireGuard/StandardWireGuardParser+Validate.swift index 5adadf86..b016c811 100644 --- a/Sources/PartoutWireGuard/StandardWireGuardParser+Validate.swift +++ b/Sources/PartoutWireGuard/StandardWireGuardParser+Validate.swift @@ -29,7 +29,7 @@ private extension WireGuard.Configuration.Builder { if !interface.addresses.isEmpty { lines.append("Address = \(interface.addresses.wgJoined)") } - let dnsEntries = interface.dns.servers + (interface.dns.searchDomains ?? []) + let dnsEntries = interface.dns.servers + (interface.dns.domains ?? []) if !dnsEntries.isEmpty { lines.append("DNS = \(dnsEntries.wgJoined)") } diff --git a/Sources/PartoutWireGuardConnection/Legacy/Mappers/LocalInterface+WireGuardKit.swift b/Sources/PartoutWireGuardConnection/Legacy/Mappers/LocalInterface+WireGuardKit.swift index b3d98eb3..1fe08a34 100644 --- a/Sources/PartoutWireGuardConnection/Legacy/Mappers/LocalInterface+WireGuardKit.swift +++ b/Sources/PartoutWireGuardConnection/Legacy/Mappers/LocalInterface+WireGuardKit.swift @@ -13,7 +13,7 @@ extension WireGuard.LocalInterface { var dnsBuilder = DNSModule.Builder() dnsBuilder.servers = wg.dns.map(\.stringRepresentation) - dnsBuilder.searchDomains = wg.dnsSearch + dnsBuilder.domains = wg.dnsSearch let dns = try dnsBuilder.build() let mtu = wg.mtu diff --git a/Tests/PartoutWireGuardTests/WireGuardParserTests.swift b/Tests/PartoutWireGuardTests/WireGuardParserTests.swift index f34c7d91..76eac919 100644 --- a/Tests/PartoutWireGuardTests/WireGuardParserTests.swift +++ b/Tests/PartoutWireGuardTests/WireGuardParserTests.swift @@ -67,7 +67,7 @@ Endpoint = 1.2.3.4:12345 var dns = DNSModule.Builder() dns.servers = ["1.2.3.4"] - dns.searchDomains = ["domain.local"] + dns.domains = ["domain.local"] sut.interface.dns = dns let builder = WireGuardModule.Builder(configurationBuilder: sut) @@ -113,7 +113,7 @@ Endpoint = 1.2.3.4:12345 // // var dns = DNSModule.Builder() // dns.servers = ["1.a.2.$%3"] -// dns.searchDomains = ["-invalid.example.com"] +// dns.domains = ["-invalid.example.com"] // sut.interface.dns = dns // // do { @@ -214,7 +214,7 @@ private extension WireGuardParserTests { builder.interface.addresses = ["1.2.3.4"] var dns = DNSModule.Builder() dns.servers = ["1.2.3.4"] - dns.searchDomains = ["domain.local"] + dns.domains = ["domain.local"] builder.interface.dns = dns } if withPeer { From acb49953650e9a70554125b325eb2345a629b32b Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 12:44:49 +0200 Subject: [PATCH 3/8] Adjust Network Extension implementation --- .../AppleNE/Modules/DNSModule+NE.swift | 76 +++++++++++++------ .../AppleNE/Modules/Profile+NE.swift | 8 +- .../AppleNE/NESettingsApplyingTests.swift | 6 +- .../AppleNE/ProfileNetworkSettingsTests.swift | 5 +- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/Sources/PartoutOS/AppleNE/Modules/DNSModule+NE.swift b/Sources/PartoutOS/AppleNE/Modules/DNSModule+NE.swift index 058ba31a..84a7a765 100644 --- a/Sources/PartoutOS/AppleNE/Modules/DNSModule+NE.swift +++ b/Sources/PartoutOS/AppleNE/Modules/DNSModule+NE.swift @@ -6,52 +6,82 @@ import NetworkExtension extension DNSModule: NESettingsApplying { public func apply(_ ctx: PartoutLoggerContext, to settings: inout NEPacketTunnelNetworkSettings) { - var dnsSettings: NEDNSSettings? + let dnsSettings: NEDNSSettings let rawServers = servers.map(\.rawValue) + // Former DNS settings are always overridden, even with empty servers switch protocolType { case .cleartext: - if !rawServers.isEmpty { - dnsSettings = NEDNSSettings(servers: rawServers) - pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })") - } else { - pp_log(ctx, .os, .info, "\t\tServers: empty") + guard !rawServers.isEmpty else { + pp_log(ctx, .os, .info, "\t\tSkip DNS settings, cleartext requires non-empty servers") + return } - + dnsSettings = NEDNSSettings(servers: rawServers) + pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })") case .https(let url): let specificSettings = NEDNSOverHTTPSSettings(servers: rawServers) specificSettings.serverURL = url dnsSettings = specificSettings pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })") pp_log(ctx, .os, .info, "\t\tDoH URL: \(url.absoluteString.asSensitiveAddress(ctx))") - case .tls(let hostname): let specificSettings = NEDNSOverTLSSettings(servers: rawServers) specificSettings.serverName = hostname dnsSettings = specificSettings pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })") pp_log(ctx, .os, .info, "\t\tDoT hostname: \(hostname.asSensitiveAddress(ctx))") - @unknown default: break } - if dnsSettings != nil { - domainName.map { - dnsSettings?.domainName = $0.rawValue - pp_log(ctx, .os, .info, "\t\tDomain: \($0.asSensitiveAddress(ctx))") - } - searchDomains.map { - guard !$0.isEmpty else { - return - } - dnsSettings?.searchDomains = $0.map(\.rawValue) - pp_log(ctx, .os, .info, "\t\tSearch domains: \($0.map { $0.asSensitiveAddress(ctx) })") - } - } else { - pp_log(ctx, .os, .info, "\t\tSkip DNS settings") + // Main domain (if set) + domainName.map { + dnsSettings.domainName = $0.rawValue + pp_log(ctx, .os, .info, "\t\tDomain: \($0.asSensitiveAddress(ctx))") + } + + // Apply domains with the given policy + let domains = searchDomains ?? [] + let domainsDescription = domains.map { $0.asSensitiveAddress(ctx) } + let searchDomains = domains.map(\.rawValue) + // + // Credit for .matchDomains: + // https://github.com/WireGuard/wireguard-apple/pull/11 + // + switch domainPolicy { + case .search: + dnsSettings.searchDomains = searchDomains + // XXX: This works around a Network Extension bug. We add the + // search domains here because .searchDomains is ineffective when + // the VPN is not the default gateway + dnsSettings.matchDomains = [""] + searchDomains + dnsSettings.matchDomainsNoSearch = false + pp_log(ctx, .os, .info, "\t\tSearch-only domains: \(domainsDescription)") + case .match: + let matchDomains = !searchDomains.isEmpty ? searchDomains : [""] + dnsSettings.searchDomains = nil + dnsSettings.matchDomains = matchDomains + dnsSettings.matchDomainsNoSearch = true + pp_log(ctx, .os, .info, "\t\tMatch-only domains: \(domainsDescription)") + default: + let matchDomains = !searchDomains.isEmpty ? searchDomains : [""] + dnsSettings.searchDomains = searchDomains + dnsSettings.matchDomains = matchDomains + dnsSettings.matchDomainsNoSearch = false + pp_log(ctx, .os, .info, "\t\tMatch/Search domains: \(domainsDescription)") + } + + // + // This is why we guard before committing .matchDomains: + // https://git.zx2c4.com/wireguard-apple/commit/?id=20bdf46792905de8862ae7641e50e0f9f99ec946 + // + assert(dnsSettings.matchDomains != nil) + if dnsSettings.servers.isEmpty { + pp_log(ctx, .os, .error, "\t\tIgnoring match domains without bootstrap DNS servers") + dnsSettings.matchDomains = nil } + // Commit to tunnel settings settings.dnsSettings = dnsSettings } } diff --git a/Sources/PartoutOS/AppleNE/Modules/Profile+NE.swift b/Sources/PartoutOS/AppleNE/Modules/Profile+NE.swift index 1c16fb52..12ba952b 100644 --- a/Sources/PartoutOS/AppleNE/Modules/Profile+NE.swift +++ b/Sources/PartoutOS/AppleNE/Modules/Profile+NE.swift @@ -55,9 +55,11 @@ extension Profile { // 4. configure DNS for domain-based routing if let dnsSettings = neSettings.dnsSettings { - - // route DNS through VPN first unless no servers provided - if !dnsSettings.servers.isEmpty { + // Route DNS through VPN first unless: + // - No servers provided + // - .matchDomains is not configured + // This is a fallback as it *SHOULD* be accomplished by DNSModule+NE + if !dnsSettings.servers.isEmpty, dnsSettings.matchDomains == nil { neSettings.dnsSettings?.matchDomains = [""] } } diff --git a/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift b/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift index fe3660a3..17bc3b61 100644 --- a/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift +++ b/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift @@ -105,18 +105,18 @@ struct NESettingsApplyingTests { let module = try DNSModule.Builder( protocolType: .cleartext, servers: ["1.1.1.1", "2.2.2.2"], - domainName: "domain.com", - searchDomains: ["one.com", "two.com"] + domains: ["domain.com", "one.com", "two.com"] ).build() var sut = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "") module.apply(.global, to: &sut) let dnsSettings = try #require(sut.dnsSettings) + let expSearchDomains = module.searchDomains?.map(\.rawValue) #expect(dnsSettings.dnsProtocol == .cleartext) #expect(dnsSettings.servers == module.servers.map(\.rawValue)) #expect(dnsSettings.domainName == module.domainName?.rawValue) - #expect(dnsSettings.searchDomains == module.searchDomains?.map(\.rawValue)) + #expect(dnsSettings.searchDomains == expSearchDomains) } @Test diff --git a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift index 307cfee9..8c9e33cc 100644 --- a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift +++ b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift @@ -95,13 +95,14 @@ struct ProfileNetworkSettingsTests { // - dnsModuleBuilder.searchDomains = ["domain.com"] + dnsModuleBuilder.domains = ["domain.com"] + dnsModuleBuilder.domainPolicy = .search sut = try Profile.Builder( modules: [connectionModule, ipModule, try dnsModuleBuilder.build()], activatingModules: true ).build().networkSettings(with: nil) - #expect(sut.dnsSettings?.matchDomains == [""]) + #expect(sut.dnsSettings?.matchDomains == ["", "domain.com"]) } // MARK: With remote info From 75158f84aa7696d63224aa4c11d28cf628a582f4 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 13:01:10 +0200 Subject: [PATCH 4/8] Default to first domain not primary --- Sources/PartoutCore/Modules/DNSModule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PartoutCore/Modules/DNSModule.swift b/Sources/PartoutCore/Modules/DNSModule.swift index 64968266..9e14dd32 100644 --- a/Sources/PartoutCore/Modules/DNSModule.swift +++ b/Sources/PartoutCore/Modules/DNSModule.swift @@ -119,7 +119,7 @@ extension DNSModule { dotHostname: String = "", domains: [String]? = nil, domainPolicy: DomainPolicy? = nil, - isFirstDomainPrimary: Bool = true, + isFirstDomainPrimary: Bool = false, routesThroughVPN: Bool? = nil ) { self.id = id From 351b4f5069f34471f73241b424d6f6ec0c6be138 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 13:13:35 +0200 Subject: [PATCH 5/8] Test NE mapping with first as domain --- .../PartoutOSTests/AppleNE/NESettingsApplyingTests.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift b/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift index 17bc3b61..0d08ba42 100644 --- a/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift +++ b/Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift @@ -100,12 +100,13 @@ struct NESettingsApplyingTests { #expect(proxySettings.exceptionList == module.bypassDomains.map(\.rawValue)) } - @Test - func givenDNS_whenApply_thenUpdatesSettings() throws { + @Test(arguments: [true, false]) + func givenDNS_whenApply_thenUpdatesSettings(isFirstDomainPrimary: Bool) throws { let module = try DNSModule.Builder( protocolType: .cleartext, servers: ["1.1.1.1", "2.2.2.2"], - domains: ["domain.com", "one.com", "two.com"] + domains: ["domain.com", "one.com", "two.com"], + isFirstDomainPrimary: isFirstDomainPrimary ).build() var sut = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "") @@ -115,6 +116,7 @@ struct NESettingsApplyingTests { let expSearchDomains = module.searchDomains?.map(\.rawValue) #expect(dnsSettings.dnsProtocol == .cleartext) #expect(dnsSettings.servers == module.servers.map(\.rawValue)) + #expect(dnsSettings.domainName == (isFirstDomainPrimary ? "domain.com" : nil)) #expect(dnsSettings.domainName == module.domainName?.rawValue) #expect(dnsSettings.searchDomains == expSearchDomains) } From c5e7a40caefd5f013f3d2c1c07924335268b5dd8 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 13:16:31 +0200 Subject: [PATCH 6/8] Extend policy tests in NE --- .../AppleNE/ProfileNetworkSettingsTests.swift | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift index 8c9e33cc..5a892916 100644 --- a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift +++ b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift @@ -64,8 +64,12 @@ struct ProfileNetworkSettingsTests { #expect(dnsSettings.matchDomains == [""]) } - @Test - func givenProfileWithoutDefaultGateway_whenGetNetworkSettings_thenAddsBogusMatchDomains() throws { + @Test(arguments: [ + nil as DNSModule.DomainPolicy?, + .match, + .search + ]) + func givenProfileWithoutDefaultGateway_whenGetNetworkSettings_thenAddsBogusMatchDomains(policy: DNSModule.DomainPolicy?) throws { let connectionModule = BogusConnectionModule() let ipModule = IPModule.Builder( ipv4: IPSettings(subnet: Subnet(rawValue: "1.2.3.4/32")!), @@ -96,13 +100,24 @@ struct ProfileNetworkSettingsTests { // dnsModuleBuilder.domains = ["domain.com"] - dnsModuleBuilder.domainPolicy = .search + dnsModuleBuilder.domainPolicy = policy sut = try Profile.Builder( modules: [connectionModule, ipModule, try dnsModuleBuilder.build()], activatingModules: true ).build().networkSettings(with: nil) - #expect(sut.dnsSettings?.matchDomains == ["", "domain.com"]) + let dns = try #require(sut.dnsSettings) + switch policy { + case .match: + #expect(dns.matchDomains == ["domain.com"]) + #expect(dns.matchDomainsNoSearch == true) + case .search: + #expect(dns.matchDomains == ["", "domain.com"]) + #expect(dns.matchDomainsNoSearch == false) + default: + #expect(dns.matchDomains == ["domain.com"]) + #expect(dns.matchDomainsNoSearch == false) + } } // MARK: With remote info From 04dc36424c1e462a2900d572435468cbf1924a98 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 13:18:51 +0200 Subject: [PATCH 7/8] [ci skip] Rename test --- Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift index 5a892916..3f27826e 100644 --- a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift +++ b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift @@ -69,7 +69,7 @@ struct ProfileNetworkSettingsTests { .match, .search ]) - func givenProfileWithoutDefaultGateway_whenGetNetworkSettings_thenAddsBogusMatchDomains(policy: DNSModule.DomainPolicy?) throws { + func givenProfile_whenGetNetworkSettings_thenAppliesProperDNSPolicy(policy: DNSModule.DomainPolicy?) throws { let connectionModule = BogusConnectionModule() let ipModule = IPModule.Builder( ipv4: IPSettings(subnet: Subnet(rawValue: "1.2.3.4/32")!), @@ -95,6 +95,7 @@ struct ProfileNetworkSettingsTests { #expect(Set(ipV4Settings.includedRoutes ?? []) == Set(expRoutesV4)) #expect(Set(ipV6Settings.includedRoutes ?? []) == Set(expRoutesV6)) + // Fallback without DNS module #expect(sut.dnsSettings?.matchDomains == [""]) // From 94ea3fd9fdc269358eb8f9fb243865599ff76f9d Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 10 Apr 2026 13:19:51 +0200 Subject: [PATCH 8/8] [ci skip] --- Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift index 3f27826e..3f15b0d8 100644 --- a/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift +++ b/Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift @@ -95,7 +95,7 @@ struct ProfileNetworkSettingsTests { #expect(Set(ipV4Settings.includedRoutes ?? []) == Set(expRoutesV4)) #expect(Set(ipV6Settings.includedRoutes ?? []) == Set(expRoutesV6)) - // Fallback without DNS module + // Fallback without domains #expect(sut.dnsSettings?.matchDomains == [""]) //