Skip to content

Commit acb4995

Browse files
committed
Adjust Network Extension implementation
1 parent 7338553 commit acb4995

4 files changed

Lines changed: 64 additions & 31 deletions

File tree

Sources/PartoutOS/AppleNE/Modules/DNSModule+NE.swift

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,82 @@ import NetworkExtension
66

77
extension DNSModule: NESettingsApplying {
88
public func apply(_ ctx: PartoutLoggerContext, to settings: inout NEPacketTunnelNetworkSettings) {
9-
var dnsSettings: NEDNSSettings?
9+
let dnsSettings: NEDNSSettings
1010
let rawServers = servers.map(\.rawValue)
1111

12+
// Former DNS settings are always overridden, even with empty servers
1213
switch protocolType {
1314
case .cleartext:
14-
if !rawServers.isEmpty {
15-
dnsSettings = NEDNSSettings(servers: rawServers)
16-
pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })")
17-
} else {
18-
pp_log(ctx, .os, .info, "\t\tServers: empty")
15+
guard !rawServers.isEmpty else {
16+
pp_log(ctx, .os, .info, "\t\tSkip DNS settings, cleartext requires non-empty servers")
17+
return
1918
}
20-
19+
dnsSettings = NEDNSSettings(servers: rawServers)
20+
pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })")
2121
case .https(let url):
2222
let specificSettings = NEDNSOverHTTPSSettings(servers: rawServers)
2323
specificSettings.serverURL = url
2424
dnsSettings = specificSettings
2525
pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })")
2626
pp_log(ctx, .os, .info, "\t\tDoH URL: \(url.absoluteString.asSensitiveAddress(ctx))")
27-
2827
case .tls(let hostname):
2928
let specificSettings = NEDNSOverTLSSettings(servers: rawServers)
3029
specificSettings.serverName = hostname
3130
dnsSettings = specificSettings
3231
pp_log(ctx, .os, .info, "\t\tServers: \(servers.map { $0.asSensitiveAddress(ctx) })")
3332
pp_log(ctx, .os, .info, "\t\tDoT hostname: \(hostname.asSensitiveAddress(ctx))")
34-
3533
@unknown default:
3634
break
3735
}
3836

39-
if dnsSettings != nil {
40-
domainName.map {
41-
dnsSettings?.domainName = $0.rawValue
42-
pp_log(ctx, .os, .info, "\t\tDomain: \($0.asSensitiveAddress(ctx))")
43-
}
44-
searchDomains.map {
45-
guard !$0.isEmpty else {
46-
return
47-
}
48-
dnsSettings?.searchDomains = $0.map(\.rawValue)
49-
pp_log(ctx, .os, .info, "\t\tSearch domains: \($0.map { $0.asSensitiveAddress(ctx) })")
50-
}
51-
} else {
52-
pp_log(ctx, .os, .info, "\t\tSkip DNS settings")
37+
// Main domain (if set)
38+
domainName.map {
39+
dnsSettings.domainName = $0.rawValue
40+
pp_log(ctx, .os, .info, "\t\tDomain: \($0.asSensitiveAddress(ctx))")
41+
}
42+
43+
// Apply domains with the given policy
44+
let domains = searchDomains ?? []
45+
let domainsDescription = domains.map { $0.asSensitiveAddress(ctx) }
46+
let searchDomains = domains.map(\.rawValue)
47+
//
48+
// Credit for .matchDomains:
49+
// https://github.com/WireGuard/wireguard-apple/pull/11
50+
//
51+
switch domainPolicy {
52+
case .search:
53+
dnsSettings.searchDomains = searchDomains
54+
// XXX: This works around a Network Extension bug. We add the
55+
// search domains here because .searchDomains is ineffective when
56+
// the VPN is not the default gateway
57+
dnsSettings.matchDomains = [""] + searchDomains
58+
dnsSettings.matchDomainsNoSearch = false
59+
pp_log(ctx, .os, .info, "\t\tSearch-only domains: \(domainsDescription)")
60+
case .match:
61+
let matchDomains = !searchDomains.isEmpty ? searchDomains : [""]
62+
dnsSettings.searchDomains = nil
63+
dnsSettings.matchDomains = matchDomains
64+
dnsSettings.matchDomainsNoSearch = true
65+
pp_log(ctx, .os, .info, "\t\tMatch-only domains: \(domainsDescription)")
66+
default:
67+
let matchDomains = !searchDomains.isEmpty ? searchDomains : [""]
68+
dnsSettings.searchDomains = searchDomains
69+
dnsSettings.matchDomains = matchDomains
70+
dnsSettings.matchDomainsNoSearch = false
71+
pp_log(ctx, .os, .info, "\t\tMatch/Search domains: \(domainsDescription)")
72+
}
73+
74+
//
75+
// This is why we guard before committing .matchDomains:
76+
// https://git.zx2c4.com/wireguard-apple/commit/?id=20bdf46792905de8862ae7641e50e0f9f99ec946
77+
//
78+
assert(dnsSettings.matchDomains != nil)
79+
if dnsSettings.servers.isEmpty {
80+
pp_log(ctx, .os, .error, "\t\tIgnoring match domains without bootstrap DNS servers")
81+
dnsSettings.matchDomains = nil
5382
}
5483

84+
// Commit to tunnel settings
5585
settings.dnsSettings = dnsSettings
5686
}
5787
}

Sources/PartoutOS/AppleNE/Modules/Profile+NE.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ extension Profile {
5555
// 4. configure DNS for domain-based routing
5656

5757
if let dnsSettings = neSettings.dnsSettings {
58-
59-
// route DNS through VPN first unless no servers provided
60-
if !dnsSettings.servers.isEmpty {
58+
// Route DNS through VPN first unless:
59+
// - No servers provided
60+
// - .matchDomains is not configured
61+
// This is a fallback as it *SHOULD* be accomplished by DNSModule+NE
62+
if !dnsSettings.servers.isEmpty, dnsSettings.matchDomains == nil {
6163
neSettings.dnsSettings?.matchDomains = [""]
6264
}
6365
}

Tests/PartoutOSTests/AppleNE/NESettingsApplyingTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,18 @@ struct NESettingsApplyingTests {
105105
let module = try DNSModule.Builder(
106106
protocolType: .cleartext,
107107
servers: ["1.1.1.1", "2.2.2.2"],
108-
domainName: "domain.com",
109-
searchDomains: ["one.com", "two.com"]
108+
domains: ["domain.com", "one.com", "two.com"]
110109
).build()
111110

112111
var sut = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "")
113112
module.apply(.global, to: &sut)
114113

115114
let dnsSettings = try #require(sut.dnsSettings)
115+
let expSearchDomains = module.searchDomains?.map(\.rawValue)
116116
#expect(dnsSettings.dnsProtocol == .cleartext)
117117
#expect(dnsSettings.servers == module.servers.map(\.rawValue))
118118
#expect(dnsSettings.domainName == module.domainName?.rawValue)
119-
#expect(dnsSettings.searchDomains == module.searchDomains?.map(\.rawValue))
119+
#expect(dnsSettings.searchDomains == expSearchDomains)
120120
}
121121

122122
@Test

Tests/PartoutOSTests/AppleNE/ProfileNetworkSettingsTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,14 @@ struct ProfileNetworkSettingsTests {
9595

9696
//
9797

98-
dnsModuleBuilder.searchDomains = ["domain.com"]
98+
dnsModuleBuilder.domains = ["domain.com"]
99+
dnsModuleBuilder.domainPolicy = .search
99100
sut = try Profile.Builder(
100101
modules: [connectionModule, ipModule, try dnsModuleBuilder.build()],
101102
activatingModules: true
102103
).build().networkSettings(with: nil)
103104

104-
#expect(sut.dnsSettings?.matchDomains == [""])
105+
#expect(sut.dnsSettings?.matchDomains == ["", "domain.com"])
105106
}
106107

107108
// MARK: With remote info

0 commit comments

Comments
 (0)