Skip to content

Commit f989901

Browse files
madrobJaewonHurjglogan
authored
Merge commit from fork
* Use DNSName for input * Use pqdn instead of fqdn. * Update Sources/DNSServer/Records/DNSName.swift Co-authored-by: J Logan <john_logan@apple.com> * Simplify description * Add test * Make fmt --------- Co-authored-by: jwhur <jaewon_hur@apple.com> Co-authored-by: jwhur <57657645+JaewonHur@users.noreply.github.com> Co-authored-by: J Logan <john_logan@apple.com>
1 parent fd2de35 commit f989901

10 files changed

Lines changed: 66 additions & 34 deletions

File tree

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ let package = Package(
205205
"ContainerPlugin",
206206
"ContainerResource",
207207
"ContainerXPC",
208+
"DNSServer",
208209
"TerminalProgress",
209210
],
210211
path: "Sources/Services/ContainerAPIService/Client"

Sources/ContainerCommands/System/DNS/DNSCreate.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ContainerAPIClient
1919
import ContainerPersistence
2020
import ContainerizationError
2121
import ContainerizationExtras
22+
import DNSServer
2223
import Foundation
2324

2425
extension Application {
@@ -48,6 +49,10 @@ extension Application {
4849
}
4950
}
5051

52+
guard let domainName = try? DNSName(domainName) else {
53+
throw ContainerizationError(.invalidArgument, message: "invalid domain name: \(domainName)")
54+
}
55+
5156
let resolver: HostDNSResolver = HostDNSResolver()
5257
do {
5358
try resolver.createDomain(name: domainName, localhost: localhostIP)
@@ -67,7 +72,7 @@ extension Application {
6772
throw error
6873
}
6974
}
70-
print(domainName)
75+
print(domainName.pqdn)
7176

7277
if localhostIP != nil {
7378
do {

Sources/ContainerCommands/System/DNS/DNSDelete.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ArgumentParser
1818
import ContainerAPIClient
1919
import ContainerizationError
2020
import ContainerizationExtras
21+
import DNSServer
2122
import Foundation
2223

2324
extension Application {
@@ -37,6 +38,10 @@ extension Application {
3738
public init() {}
3839

3940
public func run() async throws {
41+
guard let domainName = try? DNSName(domainName) else {
42+
throw ContainerizationError(.invalidArgument, message: "invalid domain name: \(domainName)")
43+
}
44+
4045
let resolver = HostDNSResolver()
4146
var localhostIP: IPAddress?
4247
do {
@@ -52,7 +57,7 @@ extension Application {
5257
}
5358

5459
guard let localhostIP else {
55-
print(domainName)
60+
print(domainName.pqdn)
5661
return
5762
}
5863

@@ -66,7 +71,7 @@ extension Application {
6671
} catch {
6772
throw ContainerizationError(.invalidState, message: "failed loading pf rules")
6873
}
69-
print(domainName)
74+
print(domainName.pqdn)
7075
}
7176
}
7277
}

Sources/ContainerCommands/System/DNS/DNSList.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ArgumentParser
1818
import ContainerAPIClient
19+
import DNSServer
1920
import Foundation
2021

2122
extension Application {
@@ -42,7 +43,7 @@ extension Application {
4243
let domains = resolver.listDomains()
4344

4445
try Output.render(
45-
json: domains,
46+
json: domains.map { $0.pqdn },
4647
display: domains.map { PrintableDomain($0) },
4748
format: format, quiet: quiet
4849
)
@@ -51,9 +52,9 @@ extension Application {
5152
}
5253

5354
private struct PrintableDomain: ListDisplayable {
54-
let domain: String
55+
let domain: DNSName
5556

56-
init(_ domain: String) {
57+
init(_ domain: DNSName) {
5758
self.domain = domain
5859
}
5960

@@ -62,10 +63,10 @@ private struct PrintableDomain: ListDisplayable {
6263
}
6364

6465
var tableRow: [String] {
65-
[domain]
66+
[domain.pqdn]
6667
}
6768

6869
var quietValue: String {
69-
domain
70+
domain.pqdn
7071
}
7172
}

Sources/DNSServer/Records/DNSName.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ public struct DNSName: Sendable, Hashable, CustomStringConvertible {
9191

9292
/// The fully-qualified domain name with trailing dot.
9393
public var description: String {
94-
labels.isEmpty ? "." : labels.joined(separator: ".") + "."
94+
labels.joined(separator: ".") + "."
95+
}
96+
97+
/// The partially-qualified domain name, which is the FQDN less the trailing dot.
98+
public var pqdn: String {
99+
labels.joined(separator: ".")
95100
}
96101

97102
/// Serialize this name into the buffer at the given offset.

Sources/Services/ContainerAPIService/Client/HostDNSResolver.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ContainerizationError
1818
import ContainerizationExtras
19+
import DNSServer
1920
import Foundation
2021

2122
/// Functions for managing local DNS domains for containers.
@@ -33,7 +34,9 @@ public struct HostDNSResolver {
3334
}
3435

3536
/// Creates a DNS resolver configuration file for domain resolved by the application.
36-
public func createDomain(name: String, localhost: IPAddress? = nil) throws {
37+
public func createDomain(name: DNSName, localhost: IPAddress? = nil) throws {
38+
let name = name.pqdn
39+
3740
let path = self.configURL.appending(path: "\(Self.containerizationPrefix)\(name)").path
3841
let fm: FileManager = FileManager.default
3942

@@ -67,7 +70,9 @@ public struct HostDNSResolver {
6770
}
6871

6972
/// Removes a DNS resolver configuration file for domain resolved by the application.
70-
public func deleteDomain(name: String) throws -> IPAddress? {
73+
public func deleteDomain(name: DNSName) throws -> IPAddress? {
74+
let name = name.pqdn
75+
7176
let path = self.configURL.appending(path: "\(Self.containerizationPrefix)\(name)").path
7277
let fm = FileManager.default
7378
guard fm.fileExists(atPath: path) else {
@@ -90,7 +95,7 @@ public struct HostDNSResolver {
9095
}
9196

9297
/// Lists application-created local DNS domains.
93-
public func listDomains() -> [String] {
98+
public func listDomains() -> [DNSName] {
9499
let fm: FileManager = FileManager.default
95100
guard
96101
let resolverPaths = try? fm.contentsOfDirectory(
@@ -105,7 +110,7 @@ public struct HostDNSResolver {
105110
resolverPaths
106111
.filter { $0.lastPathComponent.starts(with: Self.containerizationPrefix) }
107112
.compactMap { try? getDomainFromResolver(url: $0) }
108-
.sorted()
113+
.sorted { a, b in a.pqdn < b.pqdn }
109114
}
110115

111116
/// Reinitializes the macOS DNS daemon.
@@ -128,7 +133,7 @@ public struct HostDNSResolver {
128133
}
129134
}
130135

131-
private func getDomainFromResolver(url: URL) throws -> String? {
136+
private func getDomainFromResolver(url: URL) throws -> DNSName? {
132137
let text = try String(contentsOf: url, encoding: .utf8)
133138
for line in text.components(separatedBy: .newlines) {
134139
let trimmed = line.trimmingCharacters(in: .whitespaces)
@@ -140,7 +145,7 @@ public struct HostDNSResolver {
140145
continue
141146
}
142147

143-
return String(components[1])
148+
return try? DNSName(String(components[1]))
144149
}
145150

146151
return nil

Sources/Services/ContainerAPIService/Client/PacketFilter.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ContainerizationError
1818
import ContainerizationExtras
19+
import DNSServer
1920
import Foundation
2021

2122
public struct PacketFilter {
@@ -31,7 +32,7 @@ public struct PacketFilter {
3132
self.anchorsURL = anchorsURL
3233
}
3334

34-
public func createRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {
35+
public func createRedirectRule(from: IPAddress, to: IPAddress, domain: DNSName) throws {
3536
guard type(of: from) == type(of: to) else {
3637
throw ContainerizationError(.invalidArgument, message: "protocol does not match: \(from) vs. \(to)")
3738
}
@@ -45,7 +46,7 @@ public struct PacketFilter {
4546
case .v4: inet = "inet"
4647
case .v6: inet = "inet6"
4748
}
48-
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain)"
49+
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain.pqdn)"
4950

5051
var content = ""
5152
if fm.fileExists(atPath: anchorURL.path) {
@@ -62,7 +63,7 @@ public struct PacketFilter {
6263
try lines.joined(separator: "\n").write(toFile: anchorURL.path, atomically: true, encoding: .utf8)
6364
}
6465

65-
public func removeRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {
66+
public func removeRedirectRule(from: IPAddress, to: IPAddress, domain: DNSName) throws {
6667
guard type(of: from) == type(of: to) else {
6768
throw ContainerizationError(.invalidArgument, message: "protocol does not match: \(from) vs. \(to)")
6869
}
@@ -76,7 +77,7 @@ public struct PacketFilter {
7677
case .v4: inet = "inet"
7778
case .v6: inet = "inet6"
7879
}
79-
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain)"
80+
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain.pqdn)"
8081

8182
guard fm.fileExists(atPath: anchorURL.path) else {
8283
return

Tests/ContainerAPIClientTests/HostDNSResolverTest.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ContainerizationError
1818
import ContainerizationExtras
19+
import DNSServer
1920
import Foundation
2021
import Testing
2122

@@ -34,7 +35,7 @@ struct HostDNSResolverTest {
3435
defer { try? FileManager.default.removeItem(at: tempURL) }
3536

3637
let resolver = HostDNSResolver(configURL: tempURL)
37-
try resolver.createDomain(name: "foo.bar")
38+
try resolver.createDomain(name: try! DNSName("foo.bar"))
3839
let resolverConfigURL = tempURL.appending(path: "containerization.foo.bar")
3940
let actualText = try String(contentsOf: resolverConfigURL, encoding: .utf8)
4041
let expectedText = """
@@ -47,9 +48,9 @@ struct HostDNSResolverTest {
4748

4849
#expect(actualText == expectedText)
4950

50-
try resolver.createDomain(name: "bar.foo")
51+
try resolver.createDomain(name: try! DNSName("bar.foo"))
5152
let domains = resolver.listDomains()
52-
#expect(domains == ["bar.foo", "foo.bar"])
53+
#expect(domains.map { $0.pqdn } == ["bar.foo", "foo.bar"])
5354
}
5455

5556
@Test
@@ -64,9 +65,9 @@ struct HostDNSResolverTest {
6465
defer { try? FileManager.default.removeItem(at: tempURL) }
6566

6667
let resolver = HostDNSResolver(configURL: tempURL)
67-
try resolver.createDomain(name: "foo.bar")
68+
try resolver.createDomain(name: try! DNSName("foo.bar"))
6869
#expect {
69-
try resolver.createDomain(name: "foo.bar")
70+
try resolver.createDomain(name: try! DNSName("foo.bar"))
7071
} throws: { error in
7172
guard let error = error as? ContainerizationError, error.code == .exists else {
7273
return false
@@ -87,12 +88,12 @@ struct HostDNSResolverTest {
8788
defer { try? FileManager.default.removeItem(at: tempURL) }
8889

8990
let resolver = HostDNSResolver(configURL: tempURL)
90-
try resolver.createDomain(name: "foo.bar")
91-
_ = try resolver.deleteDomain(name: "foo.bar")
91+
try resolver.createDomain(name: try! DNSName("foo.bar"))
92+
_ = try resolver.deleteDomain(name: try! DNSName("foo.bar"))
9293

9394
let localhost = try! IPAddress("127.0.0.1")
94-
try resolver.createDomain(name: "bar.baz", localhost: localhost)
95-
let deletedLocalhost = try resolver.deleteDomain(name: "bar.baz")
95+
try resolver.createDomain(name: try! DNSName("bar.baz"), localhost: localhost)
96+
let deletedLocalhost = try resolver.deleteDomain(name: try! DNSName("bar.baz"))
9697
#expect(localhost == deletedLocalhost)
9798

9899
let domains = resolver.listDomains()
@@ -111,9 +112,9 @@ struct HostDNSResolverTest {
111112
defer { try? FileManager.default.removeItem(at: tempURL) }
112113

113114
let resolver = HostDNSResolver(configURL: tempURL)
114-
try resolver.createDomain(name: "foo.bar")
115+
try resolver.createDomain(name: try! DNSName("foo.bar"))
115116
#expect {
116-
_ = try resolver.deleteDomain(name: "bar.foo")
117+
_ = try resolver.deleteDomain(name: try! DNSName("bar.foo"))
117118
} throws: { error in
118119
guard let error = error as? ContainerizationError, error.code == .notFound else {
119120
return false

Tests/ContainerAPIClientTests/PacketFilterTest.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ContainerizationError
1818
import ContainerizationExtras
19+
import DNSServer
1920
import Foundation
2021
import Testing
2122

@@ -36,25 +37,25 @@ struct PacketFilterTest {
3637

3738
let pf = PacketFilter(configURL: configURL, anchorsURL: tempURL)
3839
let from1 = try! IPAddress("203.0.113.113")
39-
let domain1 = "aaa.com"
40+
let domain1 = try! DNSName("aaa.com")
4041
let to = try! IPAddress("127.0.0.1")
4142
try pf.createRedirectRule(from: from1, to: to, domain: domain1)
4243

4344
let anchorURL = tempURL.appending(path: "com.apple.container")
4445
var actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)
4546
var expectedAnchorTest = """
46-
rdr inet from any to \(from1) -> \(to) # \(domain1)\n
47+
rdr inet from any to \(from1) -> \(to) # \(domain1.pqdn)\n
4748
"""
4849

4950
#expect(actualAnchorText == expectedAnchorTest)
5051

5152
let from2 = try! IPAddress("172.31.72.1")
52-
let domain2 = "bbb.com"
53+
let domain2 = try! DNSName("bbb.com")
5354
try pf.createRedirectRule(from: from2, to: to, domain: domain2)
5455

5556
actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)
5657
expectedAnchorTest += """
57-
rdr inet from any to \(from2) -> \(to) # \(domain2)\n
58+
rdr inet from any to \(from2) -> \(to) # \(domain2.pqdn)\n
5859
"""
5960
#expect(actualAnchorText == expectedAnchorTest)
6061

Tests/DNSServerTests/RecordsTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ struct RecordsTests {
4545
#expect(name.description == "example.com.")
4646
}
4747

48+
@Test("DNS name with newline should throw")
49+
func DNSNameWithNewLine() throws {
50+
#expect(throws: DNSBindError.self) {
51+
_ = try DNSName("foo.com\n")
52+
}
53+
}
54+
4855
@Test("Root domain")
4956
func rootDomain() throws {
5057
let name = try DNSName("")

0 commit comments

Comments
 (0)