Skip to content

Commit 8d5f961

Browse files
committed
Repurpose compound domains with match/search policy
1 parent e1970dc commit 8d5f961

3 files changed

Lines changed: 60 additions & 24 deletions

File tree

Sources/PartoutCore/Modules/DNSModule.swift

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable {
1212
case tls(hostname: String)
1313
}
1414

15+
public enum DomainPolicy: Hashable, Codable, Sendable {
16+
case search
17+
case match
18+
}
19+
1520
public static let moduleType = ModuleType("DNS")
1621

1722
public let id: UniqueID
@@ -24,6 +29,8 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable {
2429

2530
public let searchDomains: [Address]?
2631

32+
public let domainPolicy: DomainPolicy?
33+
2734
public let routesThroughVPN: Bool?
2835

2936
fileprivate init(
@@ -32,13 +39,15 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable {
3239
servers: [Address],
3340
domainName: Address?,
3441
searchDomains: [Address]?,
42+
domainPolicy: DomainPolicy?,
3543
routesThroughVPN: Bool?
3644
) {
3745
self.id = id
3846
self.protocolType = protocolType
3947
self.servers = servers
4048
self.domainName = domainName
4149
self.searchDomains = searchDomains
50+
self.domainPolicy = domainPolicy
4251
self.routesThroughVPN = routesThroughVPN
4352
}
4453

@@ -50,17 +59,29 @@ public struct DNSModule: Module, BuildableType, Hashable, Codable {
5059
switch protocolType {
5160
case .cleartext:
5261
break
53-
5462
case .https(let url):
5563
builder.protocolType = .https
5664
builder.dohURL = url.absoluteString
57-
5865
case .tls(let hostname):
5966
builder.protocolType = .tls
6067
builder.dotHostname = hostname
6168
}
62-
builder.domainName = domainName?.rawValue
63-
builder.searchDomains = searchDomains?.map(\.rawValue)
69+
if let domainName {
70+
builder.isFirstDomainPrimary = true
71+
if let searchDomains {
72+
if domainName == searchDomains.first {
73+
builder.domains = searchDomains.map(\.rawValue)
74+
} else {
75+
builder.domains = [domainName.rawValue] + searchDomains.map(\.rawValue)
76+
}
77+
} else {
78+
builder.domains = [domainName.rawValue]
79+
}
80+
} else if let searchDomains {
81+
builder.isFirstDomainPrimary = false
82+
builder.domains = searchDomains.map(\.rawValue)
83+
}
84+
builder.domainPolicy = domainPolicy
6485
builder.routesThroughVPN = routesThroughVPN
6586
return builder
6687
}
@@ -78,9 +99,11 @@ extension DNSModule {
7899

79100
public var dotHostname: String
80101

81-
public var domainName: String?
102+
public var domains: [String]?
103+
104+
public var domainPolicy: DomainPolicy?
82105

83-
public var searchDomains: [String]?
106+
public var isFirstDomainPrimary: Bool
84107

85108
public var routesThroughVPN: Bool?
86109

@@ -94,17 +117,19 @@ extension DNSModule {
94117
servers: [String] = [],
95118
dohURL: String = "",
96119
dotHostname: String = "",
97-
domainName: String? = nil,
98-
searchDomains: [String]? = nil,
120+
domains: [String]? = nil,
121+
domainPolicy: DomainPolicy? = nil,
122+
isFirstDomainPrimary: Bool = true,
99123
routesThroughVPN: Bool? = nil
100124
) {
101125
self.id = id
102126
self.protocolType = protocolType
103127
self.servers = servers
104128
self.dohURL = dohURL
105129
self.dotHostname = dotHostname
106-
self.domainName = domainName
107-
self.searchDomains = searchDomains
130+
self.domains = domains
131+
self.domainPolicy = domainPolicy
132+
self.isFirstDomainPrimary = isFirstDomainPrimary
108133
self.routesThroughVPN = routesThroughVPN
109134
}
110135

@@ -118,25 +143,15 @@ extension DNSModule {
118143
}
119144
return addr
120145
}
121-
let validDomainName = try domainName.flatMap {
146+
let validDomains = try domains?.compactMap {
122147
guard !$0.isEmpty else {
123148
return nil as Address?
124149
}
125150
guard let addr = Address(rawValue: $0), !addr.isIPAddress else {
126-
throw PartoutError.invalidFields(["domainName": $0])
151+
throw PartoutError.invalidFields(["domains": $0])
127152
}
128153
return addr
129154
}
130-
let validSearchDomains = try searchDomains?.compactMap {
131-
guard !$0.isEmpty else {
132-
return nil as Address?
133-
}
134-
guard let addr = Address(rawValue: $0), !addr.isIPAddress else {
135-
throw PartoutError.invalidFields(["searchDomains": $0])
136-
}
137-
return addr
138-
}
139-
140155
let validProtocolType: ProtocolType
141156
switch protocolType {
142157
case .cleartext:
@@ -158,8 +173,9 @@ extension DNSModule {
158173
id: id,
159174
protocolType: validProtocolType,
160175
servers: validServers,
161-
domainName: validDomainName,
162-
searchDomains: validSearchDomains,
176+
domainName: isFirstDomainPrimary ? validDomains?.first : nil,
177+
searchDomains: validDomains,
178+
domainPolicy: domainPolicy,
163179
routesThroughVPN: routesThroughVPN
164180
)
165181
}

Tests/PartoutCoreTests/DNSModuleTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ struct DNSModuleTests {
3939
#expect(sut == module.builder())
4040
}
4141

42+
@Test(arguments: [true, false])
43+
func givenDomains_whenRebuild_thenIsRestored(isFirstDomainPrimary: Bool) throws {
44+
let sut = DNSModule.Builder(
45+
protocolType: .cleartext,
46+
servers: ["1.2.3.4"],
47+
domains: ["primary.example.com", "search.example.com"],
48+
isFirstDomainPrimary: isFirstDomainPrimary
49+
)
50+
let module = try sut.build()
51+
let rebuilt = module.builder()
52+
53+
#expect(module.domainName?.rawValue == (isFirstDomainPrimary ? "primary.example.com" : nil))
54+
#expect(module.searchDomains?.map(\.rawValue) == ["primary.example.com", "search.example.com"])
55+
#expect(rebuilt.domains == sut.domains)
56+
#expect(rebuilt.isFirstDomainPrimary == sut.isFirstDomainPrimary)
57+
#expect(rebuilt == sut)
58+
}
59+
4260
@Test
4361
func givenHTTPSWithoutURL_whenBuild_thenFails() {
4462
let sut = DNSModule.Builder(

scripts/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ components:
1717
properties:
1818
domainName:
1919
"$ref": "#/components/schemas/Address"
20+
domainPolicy:
21+
title: DomainPolicy
2022
id:
2123
"$ref": "#/components/schemas/UniqueID"
2224
protocolType:

0 commit comments

Comments
 (0)