mirror of
https://github.com/apple/container.git
synced 2026-08-24 02:24:19 -05:00
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>
This commit is contained in:
co-authored by
jwhur
jwhur
J Logan
parent
fd2de35440
commit
f9899013fd
@@ -205,6 +205,7 @@ let package = Package(
|
||||
"ContainerPlugin",
|
||||
"ContainerResource",
|
||||
"ContainerXPC",
|
||||
"DNSServer",
|
||||
"TerminalProgress",
|
||||
],
|
||||
path: "Sources/Services/ContainerAPIService/Client"
|
||||
|
||||
@@ -19,6 +19,7 @@ import ContainerAPIClient
|
||||
import ContainerPersistence
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
|
||||
extension Application {
|
||||
@@ -48,6 +49,10 @@ extension Application {
|
||||
}
|
||||
}
|
||||
|
||||
guard let domainName = try? DNSName(domainName) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "invalid domain name: \(domainName)")
|
||||
}
|
||||
|
||||
let resolver: HostDNSResolver = HostDNSResolver()
|
||||
do {
|
||||
try resolver.createDomain(name: domainName, localhost: localhostIP)
|
||||
@@ -67,7 +72,7 @@ extension Application {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
print(domainName)
|
||||
print(domainName.pqdn)
|
||||
|
||||
if localhostIP != nil {
|
||||
do {
|
||||
|
||||
@@ -18,6 +18,7 @@ import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
|
||||
extension Application {
|
||||
@@ -37,6 +38,10 @@ extension Application {
|
||||
public init() {}
|
||||
|
||||
public func run() async throws {
|
||||
guard let domainName = try? DNSName(domainName) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "invalid domain name: \(domainName)")
|
||||
}
|
||||
|
||||
let resolver = HostDNSResolver()
|
||||
var localhostIP: IPAddress?
|
||||
do {
|
||||
@@ -52,7 +57,7 @@ extension Application {
|
||||
}
|
||||
|
||||
guard let localhostIP else {
|
||||
print(domainName)
|
||||
print(domainName.pqdn)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -66,7 +71,7 @@ extension Application {
|
||||
} catch {
|
||||
throw ContainerizationError(.invalidState, message: "failed loading pf rules")
|
||||
}
|
||||
print(domainName)
|
||||
print(domainName.pqdn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import DNSServer
|
||||
import Foundation
|
||||
|
||||
extension Application {
|
||||
@@ -42,7 +43,7 @@ extension Application {
|
||||
let domains = resolver.listDomains()
|
||||
|
||||
try Output.render(
|
||||
json: domains,
|
||||
json: domains.map { $0.pqdn },
|
||||
display: domains.map { PrintableDomain($0) },
|
||||
format: format, quiet: quiet
|
||||
)
|
||||
@@ -51,9 +52,9 @@ extension Application {
|
||||
}
|
||||
|
||||
private struct PrintableDomain: ListDisplayable {
|
||||
let domain: String
|
||||
let domain: DNSName
|
||||
|
||||
init(_ domain: String) {
|
||||
init(_ domain: DNSName) {
|
||||
self.domain = domain
|
||||
}
|
||||
|
||||
@@ -62,10 +63,10 @@ private struct PrintableDomain: ListDisplayable {
|
||||
}
|
||||
|
||||
var tableRow: [String] {
|
||||
[domain]
|
||||
[domain.pqdn]
|
||||
}
|
||||
|
||||
var quietValue: String {
|
||||
domain
|
||||
domain.pqdn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,12 @@ public struct DNSName: Sendable, Hashable, CustomStringConvertible {
|
||||
|
||||
/// The fully-qualified domain name with trailing dot.
|
||||
public var description: String {
|
||||
labels.isEmpty ? "." : labels.joined(separator: ".") + "."
|
||||
labels.joined(separator: ".") + "."
|
||||
}
|
||||
|
||||
/// The partially-qualified domain name, which is the FQDN less the trailing dot.
|
||||
public var pqdn: String {
|
||||
labels.joined(separator: ".")
|
||||
}
|
||||
|
||||
/// Serialize this name into the buffer at the given offset.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
|
||||
/// Functions for managing local DNS domains for containers.
|
||||
@@ -33,7 +34,9 @@ public struct HostDNSResolver {
|
||||
}
|
||||
|
||||
/// Creates a DNS resolver configuration file for domain resolved by the application.
|
||||
public func createDomain(name: String, localhost: IPAddress? = nil) throws {
|
||||
public func createDomain(name: DNSName, localhost: IPAddress? = nil) throws {
|
||||
let name = name.pqdn
|
||||
|
||||
let path = self.configURL.appending(path: "\(Self.containerizationPrefix)\(name)").path
|
||||
let fm: FileManager = FileManager.default
|
||||
|
||||
@@ -67,7 +70,9 @@ public struct HostDNSResolver {
|
||||
}
|
||||
|
||||
/// Removes a DNS resolver configuration file for domain resolved by the application.
|
||||
public func deleteDomain(name: String) throws -> IPAddress? {
|
||||
public func deleteDomain(name: DNSName) throws -> IPAddress? {
|
||||
let name = name.pqdn
|
||||
|
||||
let path = self.configURL.appending(path: "\(Self.containerizationPrefix)\(name)").path
|
||||
let fm = FileManager.default
|
||||
guard fm.fileExists(atPath: path) else {
|
||||
@@ -90,7 +95,7 @@ public struct HostDNSResolver {
|
||||
}
|
||||
|
||||
/// Lists application-created local DNS domains.
|
||||
public func listDomains() -> [String] {
|
||||
public func listDomains() -> [DNSName] {
|
||||
let fm: FileManager = FileManager.default
|
||||
guard
|
||||
let resolverPaths = try? fm.contentsOfDirectory(
|
||||
@@ -105,7 +110,7 @@ public struct HostDNSResolver {
|
||||
resolverPaths
|
||||
.filter { $0.lastPathComponent.starts(with: Self.containerizationPrefix) }
|
||||
.compactMap { try? getDomainFromResolver(url: $0) }
|
||||
.sorted()
|
||||
.sorted { a, b in a.pqdn < b.pqdn }
|
||||
}
|
||||
|
||||
/// Reinitializes the macOS DNS daemon.
|
||||
@@ -128,7 +133,7 @@ public struct HostDNSResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private func getDomainFromResolver(url: URL) throws -> String? {
|
||||
private func getDomainFromResolver(url: URL) throws -> DNSName? {
|
||||
let text = try String(contentsOf: url, encoding: .utf8)
|
||||
for line in text.components(separatedBy: .newlines) {
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
@@ -140,7 +145,7 @@ public struct HostDNSResolver {
|
||||
continue
|
||||
}
|
||||
|
||||
return String(components[1])
|
||||
return try? DNSName(String(components[1]))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
|
||||
public struct PacketFilter {
|
||||
@@ -31,7 +32,7 @@ public struct PacketFilter {
|
||||
self.anchorsURL = anchorsURL
|
||||
}
|
||||
|
||||
public func createRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {
|
||||
public func createRedirectRule(from: IPAddress, to: IPAddress, domain: DNSName) throws {
|
||||
guard type(of: from) == type(of: to) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "protocol does not match: \(from) vs. \(to)")
|
||||
}
|
||||
@@ -45,7 +46,7 @@ public struct PacketFilter {
|
||||
case .v4: inet = "inet"
|
||||
case .v6: inet = "inet6"
|
||||
}
|
||||
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain)"
|
||||
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain.pqdn)"
|
||||
|
||||
var content = ""
|
||||
if fm.fileExists(atPath: anchorURL.path) {
|
||||
@@ -62,7 +63,7 @@ public struct PacketFilter {
|
||||
try lines.joined(separator: "\n").write(toFile: anchorURL.path, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
public func removeRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {
|
||||
public func removeRedirectRule(from: IPAddress, to: IPAddress, domain: DNSName) throws {
|
||||
guard type(of: from) == type(of: to) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "protocol does not match: \(from) vs. \(to)")
|
||||
}
|
||||
@@ -76,7 +77,7 @@ public struct PacketFilter {
|
||||
case .v4: inet = "inet"
|
||||
case .v6: inet = "inet6"
|
||||
}
|
||||
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain)"
|
||||
let redirectRule = "rdr \(inet) from any to \(from.description) -> \(to.description) # \(domain.pqdn)"
|
||||
|
||||
guard fm.fileExists(atPath: anchorURL.path) else {
|
||||
return
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@@ -34,7 +35,7 @@ struct HostDNSResolverTest {
|
||||
defer { try? FileManager.default.removeItem(at: tempURL) }
|
||||
|
||||
let resolver = HostDNSResolver(configURL: tempURL)
|
||||
try resolver.createDomain(name: "foo.bar")
|
||||
try resolver.createDomain(name: try! DNSName("foo.bar"))
|
||||
let resolverConfigURL = tempURL.appending(path: "containerization.foo.bar")
|
||||
let actualText = try String(contentsOf: resolverConfigURL, encoding: .utf8)
|
||||
let expectedText = """
|
||||
@@ -47,9 +48,9 @@ struct HostDNSResolverTest {
|
||||
|
||||
#expect(actualText == expectedText)
|
||||
|
||||
try resolver.createDomain(name: "bar.foo")
|
||||
try resolver.createDomain(name: try! DNSName("bar.foo"))
|
||||
let domains = resolver.listDomains()
|
||||
#expect(domains == ["bar.foo", "foo.bar"])
|
||||
#expect(domains.map { $0.pqdn } == ["bar.foo", "foo.bar"])
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,9 +65,9 @@ struct HostDNSResolverTest {
|
||||
defer { try? FileManager.default.removeItem(at: tempURL) }
|
||||
|
||||
let resolver = HostDNSResolver(configURL: tempURL)
|
||||
try resolver.createDomain(name: "foo.bar")
|
||||
try resolver.createDomain(name: try! DNSName("foo.bar"))
|
||||
#expect {
|
||||
try resolver.createDomain(name: "foo.bar")
|
||||
try resolver.createDomain(name: try! DNSName("foo.bar"))
|
||||
} throws: { error in
|
||||
guard let error = error as? ContainerizationError, error.code == .exists else {
|
||||
return false
|
||||
@@ -87,12 +88,12 @@ struct HostDNSResolverTest {
|
||||
defer { try? FileManager.default.removeItem(at: tempURL) }
|
||||
|
||||
let resolver = HostDNSResolver(configURL: tempURL)
|
||||
try resolver.createDomain(name: "foo.bar")
|
||||
_ = try resolver.deleteDomain(name: "foo.bar")
|
||||
try resolver.createDomain(name: try! DNSName("foo.bar"))
|
||||
_ = try resolver.deleteDomain(name: try! DNSName("foo.bar"))
|
||||
|
||||
let localhost = try! IPAddress("127.0.0.1")
|
||||
try resolver.createDomain(name: "bar.baz", localhost: localhost)
|
||||
let deletedLocalhost = try resolver.deleteDomain(name: "bar.baz")
|
||||
try resolver.createDomain(name: try! DNSName("bar.baz"), localhost: localhost)
|
||||
let deletedLocalhost = try resolver.deleteDomain(name: try! DNSName("bar.baz"))
|
||||
#expect(localhost == deletedLocalhost)
|
||||
|
||||
let domains = resolver.listDomains()
|
||||
@@ -111,9 +112,9 @@ struct HostDNSResolverTest {
|
||||
defer { try? FileManager.default.removeItem(at: tempURL) }
|
||||
|
||||
let resolver = HostDNSResolver(configURL: tempURL)
|
||||
try resolver.createDomain(name: "foo.bar")
|
||||
try resolver.createDomain(name: try! DNSName("foo.bar"))
|
||||
#expect {
|
||||
_ = try resolver.deleteDomain(name: "bar.foo")
|
||||
_ = try resolver.deleteDomain(name: try! DNSName("bar.foo"))
|
||||
} throws: { error in
|
||||
guard let error = error as? ContainerizationError, error.code == .notFound else {
|
||||
return false
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import DNSServer
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@@ -36,25 +37,25 @@ struct PacketFilterTest {
|
||||
|
||||
let pf = PacketFilter(configURL: configURL, anchorsURL: tempURL)
|
||||
let from1 = try! IPAddress("203.0.113.113")
|
||||
let domain1 = "aaa.com"
|
||||
let domain1 = try! DNSName("aaa.com")
|
||||
let to = try! IPAddress("127.0.0.1")
|
||||
try pf.createRedirectRule(from: from1, to: to, domain: domain1)
|
||||
|
||||
let anchorURL = tempURL.appending(path: "com.apple.container")
|
||||
var actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)
|
||||
var expectedAnchorTest = """
|
||||
rdr inet from any to \(from1) -> \(to) # \(domain1)\n
|
||||
rdr inet from any to \(from1) -> \(to) # \(domain1.pqdn)\n
|
||||
"""
|
||||
|
||||
#expect(actualAnchorText == expectedAnchorTest)
|
||||
|
||||
let from2 = try! IPAddress("172.31.72.1")
|
||||
let domain2 = "bbb.com"
|
||||
let domain2 = try! DNSName("bbb.com")
|
||||
try pf.createRedirectRule(from: from2, to: to, domain: domain2)
|
||||
|
||||
actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)
|
||||
expectedAnchorTest += """
|
||||
rdr inet from any to \(from2) -> \(to) # \(domain2)\n
|
||||
rdr inet from any to \(from2) -> \(to) # \(domain2.pqdn)\n
|
||||
"""
|
||||
#expect(actualAnchorText == expectedAnchorTest)
|
||||
|
||||
|
||||
@@ -45,6 +45,13 @@ struct RecordsTests {
|
||||
#expect(name.description == "example.com.")
|
||||
}
|
||||
|
||||
@Test("DNS name with newline should throw")
|
||||
func DNSNameWithNewLine() throws {
|
||||
#expect(throws: DNSBindError.self) {
|
||||
_ = try DNSName("foo.com\n")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Root domain")
|
||||
func rootDomain() throws {
|
||||
let name = try DNSName("")
|
||||
|
||||
Reference in New Issue
Block a user