mirror of
https://github.com/apple/container.git
synced 2026-10-04 13:27:47 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Protocol for implementing custom DNS handlers.
|
||||
public protocol DNSHandler {
|
||||
/// Attempt to answer a DNS query
|
||||
/// - Parameter query: the query message
|
||||
/// - Throws: a server failure occurred during the query
|
||||
/// - Returns: The response message for the query, or nil if the request
|
||||
/// is not within the scope of the handler.
|
||||
func answer(query: Message) async throws -> Message?
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
|
||||
extension DNSServer {
|
||||
/// Handles the DNS request.
|
||||
/// - Parameters:
|
||||
/// - outbound: The NIOAsyncChannelOutboundWriter for which to respond.
|
||||
/// - packet: The request packet.
|
||||
func handle(
|
||||
outbound: NIOAsyncChannelOutboundWriter<AddressedEnvelope<ByteBuffer>>,
|
||||
packet: inout AddressedEnvelope<ByteBuffer>
|
||||
) async throws {
|
||||
let chunkSize = 512
|
||||
var data = Data()
|
||||
|
||||
self.log?.debug("reading data")
|
||||
while packet.data.readableBytes > 0 {
|
||||
if let chunk = packet.data.readBytes(length: min(chunkSize, packet.data.readableBytes)) {
|
||||
data.append(contentsOf: chunk)
|
||||
}
|
||||
}
|
||||
|
||||
self.log?.debug("deserializing message")
|
||||
let query = try Message(deserialize: data)
|
||||
self.log?.debug("processing query: \(query.questions)")
|
||||
|
||||
// always send response
|
||||
let responseData: Data
|
||||
do {
|
||||
self.log?.debug("awaiting processing")
|
||||
var response =
|
||||
try await handler.answer(query: query)
|
||||
?? Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .notImplemented,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
|
||||
// no responses
|
||||
if response.answers.isEmpty {
|
||||
response.returnCode = .nonExistentDomain
|
||||
}
|
||||
|
||||
self.log?.debug("serializing response")
|
||||
responseData = try response.serialize()
|
||||
} catch {
|
||||
self.log?.error("error processing message from \(query): \(error)")
|
||||
let response = Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .notImplemented,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
responseData = try response.serialize()
|
||||
}
|
||||
|
||||
self.log?.debug("sending response for \(query.id)")
|
||||
let rData = ByteBuffer(bytes: responseData)
|
||||
try? await outbound.write(AddressedEnvelope(remoteAddress: packet.remoteAddress, data: rData))
|
||||
|
||||
self.log?.debug("processing done")
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
import Logging
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
|
||||
/// Provides a DNS server.
|
||||
/// - Parameters:
|
||||
/// - host: The host address on which to listen.
|
||||
/// - port: The port for the server to listen.
|
||||
public struct DNSServer {
|
||||
public var handler: DNSHandler
|
||||
let log: Logger?
|
||||
|
||||
public init(
|
||||
handler: DNSHandler,
|
||||
log: Logger? = nil
|
||||
) {
|
||||
self.handler = handler
|
||||
self.log = log
|
||||
}
|
||||
|
||||
public func run(host: String, port: Int) async throws {
|
||||
// TODO: TCP server
|
||||
let srv = try await DatagramBootstrap(group: NIOSingletons.posixEventLoopGroup)
|
||||
.channelOption(.socketOption(.so_reuseaddr), value: 1)
|
||||
.bind(host: host, port: port)
|
||||
.flatMapThrowing { channel in
|
||||
try NIOAsyncChannel(
|
||||
wrappingChannelSynchronously: channel,
|
||||
configuration: NIOAsyncChannel.Configuration(
|
||||
inboundType: AddressedEnvelope<ByteBuffer>.self,
|
||||
outboundType: AddressedEnvelope<ByteBuffer>.self
|
||||
)
|
||||
)
|
||||
}
|
||||
.get()
|
||||
|
||||
try await srv.executeThenClose { inbound, outbound in
|
||||
for try await var packet in inbound {
|
||||
try await self.handle(outbound: outbound, packet: &packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func run(socketPath: String) async throws {
|
||||
// TODO: TCP server
|
||||
let srv = try await DatagramBootstrap(group: NIOSingletons.posixEventLoopGroup)
|
||||
.bind(unixDomainSocketPath: socketPath, cleanupExistingSocketFile: true)
|
||||
.flatMapThrowing { channel in
|
||||
try NIOAsyncChannel(
|
||||
wrappingChannelSynchronously: channel,
|
||||
configuration: NIOAsyncChannel.Configuration(
|
||||
inboundType: AddressedEnvelope<ByteBuffer>.self,
|
||||
outboundType: AddressedEnvelope<ByteBuffer>.self
|
||||
)
|
||||
)
|
||||
}
|
||||
.get()
|
||||
|
||||
try await srv.executeThenClose { inbound, outbound in
|
||||
for try await var packet in inbound {
|
||||
log?.debug("received packet from \(packet.remoteAddress)")
|
||||
try await self.handle(outbound: outbound, packet: &packet)
|
||||
log?.debug("sent packet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() async throws {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Delegates a query sequentially to handlers until one provides a response.
|
||||
public struct CompositeResolver: DNSHandler {
|
||||
private let handlers: [DNSHandler]
|
||||
|
||||
public init(handlers: [DNSHandler]) {
|
||||
self.handlers = handlers
|
||||
}
|
||||
|
||||
public func answer(query: Message) async throws -> Message? {
|
||||
for handler in self.handlers {
|
||||
if let response = try await handler.answer(query: query) {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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 DNS
|
||||
|
||||
/// Handler that uses table lookup to resolve hostnames.
|
||||
public struct HostTableResolver: DNSHandler {
|
||||
public let hosts4: [String: IPv4]
|
||||
private let ttl: UInt32
|
||||
|
||||
public init(hosts4: [String: IPv4], ttl: UInt32 = 300) {
|
||||
self.hosts4 = hosts4
|
||||
self.ttl = ttl
|
||||
}
|
||||
|
||||
public func answer(query: Message) async throws -> Message? {
|
||||
let question = query.questions[0]
|
||||
let record: ResourceRecord?
|
||||
switch question.type {
|
||||
case ResourceRecordType.host:
|
||||
record = answerHost(question: question)
|
||||
case ResourceRecordType.nameServer,
|
||||
ResourceRecordType.alias,
|
||||
ResourceRecordType.startOfAuthority,
|
||||
ResourceRecordType.pointer,
|
||||
ResourceRecordType.mailExchange,
|
||||
ResourceRecordType.text,
|
||||
ResourceRecordType.host6,
|
||||
ResourceRecordType.service,
|
||||
ResourceRecordType.incrementalZoneTransfer,
|
||||
ResourceRecordType.standardZoneTransfer,
|
||||
ResourceRecordType.all:
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .notImplemented,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
default:
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .formatError,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
}
|
||||
|
||||
guard let record else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .noError,
|
||||
questions: query.questions,
|
||||
answers: [record]
|
||||
)
|
||||
}
|
||||
|
||||
private func answerHost(question: Question) -> ResourceRecord? {
|
||||
guard let ip = hosts4[question.name] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return HostRecord<IPv4>(name: question.name, ttl: ttl, ip: ip)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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 DNS
|
||||
|
||||
/// Handler that returns NXDOMAIN for all hostnames.
|
||||
public struct NxDomainResolver: DNSHandler {
|
||||
private let ttl: UInt32
|
||||
|
||||
public init(ttl: UInt32 = 300) {
|
||||
self.ttl = ttl
|
||||
}
|
||||
|
||||
public func answer(query: Message) async throws -> Message? {
|
||||
let question = query.questions[0]
|
||||
switch question.type {
|
||||
case ResourceRecordType.host:
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .nonExistentDomain,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
case ResourceRecordType.nameServer,
|
||||
ResourceRecordType.alias,
|
||||
ResourceRecordType.startOfAuthority,
|
||||
ResourceRecordType.pointer,
|
||||
ResourceRecordType.mailExchange,
|
||||
ResourceRecordType.text,
|
||||
ResourceRecordType.host6,
|
||||
ResourceRecordType.service,
|
||||
ResourceRecordType.incrementalZoneTransfer,
|
||||
ResourceRecordType.standardZoneTransfer,
|
||||
ResourceRecordType.all:
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .notImplemented,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
default:
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .formatError,
|
||||
questions: query.questions,
|
||||
answers: []
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Pass standard queries to a delegate handler.
|
||||
public struct StandardQueryValidator: DNSHandler {
|
||||
private let handler: DNSHandler
|
||||
|
||||
/// Create the handler.
|
||||
/// - Parameter delegate: the handler that receives valid queries
|
||||
public init(handler: DNSHandler) {
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
/// Ensures the query is valid before forwarding it to the delegate.
|
||||
/// - Parameter msg: the query message
|
||||
/// - Returns: the delegate response if the query is valid, and an
|
||||
/// error response otherwise
|
||||
public func answer(query: Message) async throws -> Message? {
|
||||
// Reject response messages.
|
||||
guard query.type == .query else {
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .formatError,
|
||||
questions: query.questions
|
||||
)
|
||||
}
|
||||
|
||||
// Standard DNS servers handle only query operations.
|
||||
guard query.operationCode == .query else {
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .notImplemented,
|
||||
questions: query.questions
|
||||
)
|
||||
}
|
||||
|
||||
// Standard DNS servers only handle messages with exactly one question.
|
||||
guard query.questions.count == 1 else {
|
||||
return Message(
|
||||
id: query.id,
|
||||
type: .response,
|
||||
returnCode: .formatError,
|
||||
questions: query.questions
|
||||
)
|
||||
}
|
||||
|
||||
return try await handler.answer(query: query)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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 DNS
|
||||
import Foundation
|
||||
|
||||
public typealias Message = DNS.Message
|
||||
public typealias ResourceRecord = DNS.ResourceRecord
|
||||
public typealias HostRecord = DNS.HostRecord
|
||||
public typealias IPv4 = DNS.IPv4
|
||||
public typealias IPv6 = DNS.IPv6
|
||||
public typealias ReturnCode = DNS.ReturnCode
|
||||
|
||||
public enum DNSResolverError: Swift.Error, CustomStringConvertible {
|
||||
case serverError(_ msg: String)
|
||||
case invalidHandlerSpec(_ spec: String)
|
||||
case unsupportedHandlerType(_ t: String)
|
||||
case invalidIP(_ v: String)
|
||||
case invalidHandlerOption(_ v: String)
|
||||
case handlerConfigError(_ msg: String)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .serverError(let msg):
|
||||
return "server error: \(msg)"
|
||||
case .invalidHandlerSpec(let msg):
|
||||
return "invalid handler spec: \(msg)"
|
||||
case .unsupportedHandlerType(let t):
|
||||
return "unsupported handler type specified: \(t)"
|
||||
case .invalidIP(let ip):
|
||||
return "invalid IP specified: \(ip)"
|
||||
case .invalidHandlerOption(let v):
|
||||
return "invalid handler option specified: \(v)"
|
||||
case .handlerConfigError(let msg):
|
||||
return "error configuring handler: \(msg)"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user