diff --git a/Sources/Containerization/DNSConfiguration.swift b/Sources/Containerization/DNSConfiguration.swift index 8c9e601f..dc0abe47 100644 --- a/Sources/Containerization/DNSConfiguration.swift +++ b/Sources/Containerization/DNSConfiguration.swift @@ -14,6 +14,8 @@ // limitations under the License. //===----------------------------------------------------------------------===// +import ContainerizationExtras + /// DNS configuration for a container. The values will be used to /// construct /etc/resolv.conf for a given container. public struct DNS: Sendable { @@ -41,6 +43,26 @@ public struct DNS: Sendable { self.searchDomains = searchDomains self.options = options } + + /// Validates the DNS configuration. + /// + /// Ensures that all nameserver entries are valid IPv4 or IPv6 addresses. + /// Arbitrary hostnames are not permitted as nameservers. + /// + /// - Throws: ``ContainerizationError`` with code `.invalidArgument` if + /// any nameserver is not a valid IP address. + public func validate() throws { + for nameserver in nameservers { + let isValidIPv4 = (try? IPv4Address(nameserver)) != nil + let isValidIPv6 = (try? IPv6Address(nameserver)) != nil + if !isValidIPv4 && !isValidIPv6 { + throw ContainerizationError( + .invalidArgument, + message: "nameserver '\(nameserver)' is not a valid IPv4 or IPv6 address" + ) + } + } + } } extension DNS { diff --git a/Sources/Containerization/Vminitd.swift b/Sources/Containerization/Vminitd.swift index f2caa15e..6edb63c9 100644 --- a/Sources/Containerization/Vminitd.swift +++ b/Sources/Containerization/Vminitd.swift @@ -406,6 +406,7 @@ extension Vminitd { /// Configure DNS within the sandbox's environment. public func configureDNS(config: DNS, location: String) async throws { + try config.validate() _ = try await client.configureDns( .with { $0.location = location diff --git a/Tests/ContainerizationTests/DNSTests.swift b/Tests/ContainerizationTests/DNSTests.swift index a003b919..fe866b97 100644 --- a/Tests/ContainerizationTests/DNSTests.swift +++ b/Tests/ContainerizationTests/DNSTests.swift @@ -51,4 +51,34 @@ struct DNSTests { let expected = "nameserver 8.8.8.8\n" #expect(dns.resolvConf == expected) } + + @Test func dnsValidateAcceptsValidIPv4Nameservers() throws { + let dns = DNS(nameservers: ["8.8.8.8", "1.1.1.1"]) + #expect(throws: Never.self) { try dns.validate() } + } + + @Test func dnsValidateAcceptsValidIPv6Nameservers() throws { + let dns = DNS(nameservers: ["2001:4860:4860::8888", "::1"]) + #expect(throws: Never.self) { try dns.validate() } + } + + @Test func dnsValidateAcceptsMixedIPv4AndIPv6Nameservers() throws { + let dns = DNS(nameservers: ["8.8.8.8", "2001:4860:4860::8844"]) + #expect(throws: Never.self) { try dns.validate() } + } + + @Test func dnsValidateAcceptsEmptyNameservers() throws { + let dns = DNS(nameservers: []) + #expect(throws: Never.self) { try dns.validate() } + } + + @Test func dnsValidateRejectsHostname() { + let dns = DNS(nameservers: ["dns.example.com"]) + #expect(throws: (any Error).self) { try dns.validate() } + } + + @Test func dnsValidateRejectsInvalidAddress() { + let dns = DNS(nameservers: ["not-an-ip"]) + #expect(throws: (any Error).self) { try dns.validate() } + } }