mirror of
https://github.com/apple/container.git
synced 2026-07-13 13:07:06 +00:00
4a8a8a6f89
- Closes #1268. - The types we were using weren't very usable with Swift 6 structured concurrency. - Implements just the subset of records that we use. - Use notImplemented instead of formatError for unknown record types. - Use pure actor for LocalhostDNSHandler now that we have sendable types. - Use DNSName as key for table lookups in LocalhostDNSHandler and HostTableResolver. - Utilize dot-suffixed domain names everywhere in the lookup chain. - Huge thanks to @manojmahapatra and @katiewasnothere for their diligent and patient review :D
114 lines
4.7 KiB
Swift
114 lines
4.7 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2026 Apple Inc. and the container project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Foundation
|
|
|
|
/// A DNS question (query).
|
|
///
|
|
/// All domain names in a `Question` are canonical DNS names — fully-qualified
|
|
/// with a trailing dot (e.g. `"example.com."`). This invariant is not enforced
|
|
/// automatically; callers are responsible for supplying canonical names.
|
|
public struct Question: Sendable, CustomStringConvertible {
|
|
/// The fully-qualified domain name being queried, with a trailing dot (e.g. `"example.com."`).
|
|
public var name: String
|
|
|
|
/// The record type being requested.
|
|
public var type: ResourceRecordType
|
|
|
|
/// The record class (usually .internet).
|
|
public var recordClass: ResourceRecordClass
|
|
|
|
/// Creates a DNS question.
|
|
///
|
|
/// - Parameter name: The fully-qualified domain name to query, with a trailing dot
|
|
/// (e.g. `"example.com."`). Supplying a name without a trailing dot will produce
|
|
/// lookup mismatches against canonically-stored records.
|
|
/// - Parameter type: The record type being requested.
|
|
/// - Parameter recordClass: The record class (usually `.internet`).
|
|
public init(
|
|
name: String,
|
|
type: ResourceRecordType = .host,
|
|
recordClass: ResourceRecordClass = .internet
|
|
) {
|
|
self.name = name
|
|
self.type = type
|
|
self.recordClass = recordClass
|
|
}
|
|
|
|
public var description: String {
|
|
"\(name) \(type) \(recordClass)"
|
|
}
|
|
|
|
/// Serialize this question into the buffer.
|
|
public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
let startOffset = offset
|
|
var offset = offset
|
|
|
|
// Write name
|
|
let normalized = name.hasSuffix(".") ? String(name.dropLast()) : name
|
|
let dnsName = try DNSName(labels: normalized.isEmpty ? [] : normalized.split(separator: ".", omittingEmptySubsequences: false).map(String.init))
|
|
offset = try dnsName.appendBuffer(&buffer, offset: offset)
|
|
|
|
// Write type (big-endian)
|
|
guard let newOffset = buffer.copyIn(as: UInt16.self, value: type.rawValue.bigEndian, offset: offset) else {
|
|
throw DNSBindError.marshalFailure(type: "Question", field: "type")
|
|
}
|
|
offset = newOffset
|
|
|
|
// Write class (big-endian)
|
|
guard let newOffset = buffer.copyIn(as: UInt16.self, value: recordClass.rawValue.bigEndian, offset: offset) else {
|
|
throw DNSBindError.marshalFailure(type: "Question", field: "class")
|
|
}
|
|
|
|
let expectedOffset = startOffset + dnsName.size + 4
|
|
guard newOffset == expectedOffset else {
|
|
throw DNSBindError.unexpectedOffset(type: "Question", expected: expectedOffset, actual: newOffset)
|
|
}
|
|
return newOffset
|
|
}
|
|
|
|
/// Deserialize a question from the buffer.
|
|
public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int, messageStart: Int = 0) throws -> Int {
|
|
var offset = offset
|
|
|
|
// Read name
|
|
var dnsName = DNSName()
|
|
offset = try dnsName.bindBuffer(&buffer, offset: offset, messageStart: messageStart)
|
|
self.name = dnsName.description
|
|
|
|
// Read type (big-endian)
|
|
guard let (newOffset, rawType) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw DNSBindError.unmarshalFailure(type: "Question", field: "type")
|
|
}
|
|
guard let qtype = ResourceRecordType(rawValue: UInt16(bigEndian: rawType)) else {
|
|
throw DNSBindError.unsupportedValue(type: "Question", field: "type")
|
|
}
|
|
self.type = qtype
|
|
offset = newOffset
|
|
|
|
// Read class (big-endian)
|
|
guard let (newOffset, rawClass) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw DNSBindError.unmarshalFailure(type: "Question", field: "class")
|
|
}
|
|
guard let qclass = ResourceRecordClass(rawValue: UInt16(bigEndian: rawClass)) else {
|
|
throw DNSBindError.unsupportedValue(type: "Question", field: "class")
|
|
}
|
|
self.recordClass = qclass
|
|
|
|
return newOffset
|
|
}
|
|
}
|