mirror of
https://github.com/apple/container.git
synced 2026-10-20 04:50:59 +00:00
- Closes #457. - A container can now declare additional DNS names for a network attachment with a repeatable `alias` property on `--network`, including subtree aliases that match every name below a domain. - With the default domain `test`, this container answers to `gateway.test` by its name, to `api.test` by the exact alias, and to `web.gateway.test`, `a.b.gateway.test` and every other name below `gateway.test` by the subtree alias. The motivating case is a gateway container in front of a local Kubernetes cluster that answers for `*.gateway.test`, with an ingress controller routing on the `Host` header. - An alias is an exact name, or a name with a leading dot that matches every name below it. A name without a dot gets the default domain, as container names do. A subtree alias must be below a name the same attachment owns, and exact names are unique across all containers, so a container cannot claim another container's namespace. The rules for one attachment are checked when a container is created, again when a stored container starts, and again by the network helper when it registers the aliases. See `docs/architecture/dns.md` for the design, the invariants and the security considerations.
87 lines
3.5 KiB
Swift
87 lines
3.5 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 ContainerNetworkClient
|
|
|
|
/// Chooses the best answer to a name from the matches that individual networks report.
|
|
///
|
|
/// A hostname or an exact alias is decisive. Both are unique across all networks, so no other network
|
|
/// can have a better match, and the search can stop. A subtree alias is not decisive: another network
|
|
/// may have a hostname, an exact alias, or a subtree alias with a longer base, which is the most
|
|
/// specific. Two different subtrees that match a name and have the same number of labels in the base
|
|
/// must have the same base, and a base belongs to one container, so there is no tie to break.
|
|
struct NetworkMatchSelector {
|
|
/// The best match offered so far.
|
|
private(set) var best: NetworkMatch?
|
|
|
|
/// Considers a match reported by a network.
|
|
///
|
|
/// - Returns: `true` if the match is decisive and the search can stop.
|
|
@discardableResult
|
|
mutating func offer(_ match: NetworkMatch) -> Bool {
|
|
switch match.kind {
|
|
case .hostname, .alias:
|
|
best = match
|
|
return true
|
|
case .subtree(let baseLabelCount):
|
|
switch best?.kind {
|
|
case .hostname?, .alias?:
|
|
// A decisive match was already found, and nothing beats it.
|
|
return true
|
|
case .subtree(let bestBaseLabelCount)?:
|
|
if bestBaseLabelCount >= baseLabelCount {
|
|
return false
|
|
}
|
|
case nil:
|
|
break
|
|
}
|
|
best = match
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NetworkMatchSelector {
|
|
/// Looks the name up on each source, and returns the best match.
|
|
///
|
|
/// The sources are asked in order, and the search stops at the first decisive match. A source that
|
|
/// throws is skipped, and reported to `onFailure`. A network whose helper is starting up or shutting
|
|
/// down cannot answer, and it should not make the answers of the other networks fail.
|
|
///
|
|
/// - Parameters:
|
|
/// - sources: The networks to ask, each with a name for reporting and a function that does the lookup.
|
|
/// - onFailure: Called with the name of a source and the error when a source throws.
|
|
static func select(
|
|
from sources: [(name: String, lookup: @Sendable () async throws -> NetworkMatch?)],
|
|
onFailure: @Sendable (String, any Error) -> Void
|
|
) async -> NetworkMatch? {
|
|
var selector = NetworkMatchSelector()
|
|
for source in sources {
|
|
let match: NetworkMatch?
|
|
do {
|
|
match = try await source.lookup()
|
|
} catch {
|
|
onFailure(source.name, error)
|
|
continue
|
|
}
|
|
if let match, selector.offer(match) {
|
|
break
|
|
}
|
|
}
|
|
return selector.best
|
|
}
|
|
}
|