mirror of
https://github.com/apple/container.git
synced 2026-09-28 01:46:25 -04:00
Reorganize Swift package targets for network plugin. (#1615)
- Part of #1404. - Updates containerization to 0.33.2. - Reorganizes network plugin targets into: - `ContainerNetworkClient` - network plugin client and default types - `ContainerNetworkServer` - separate protocols for `Network` which manages the underlying virtual network, `NetworkService`, which takes a network and implements the API, and an actor `NetworkHarness` that marshals between the API and the XPC protocol. The service-harness separation will help us ensure XPC protocol compatibility in both directions as we evolve the plugin APIs. - Removes `disableAllocator()` which is no longer used since #1545 switched over to using XPC connections between runtime and network plugin instances to track whether a network has attached containers.
This commit is contained in:
@@ -18,7 +18,6 @@ import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerAPIService
|
||||
import ContainerLog
|
||||
import ContainerNetworkService
|
||||
import ContainerPersistence
|
||||
import ContainerPlugin
|
||||
import ContainerResource
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
import ArgumentParser
|
||||
import ContainerLog
|
||||
import ContainerNetworkService
|
||||
import ContainerNetworkServiceClient
|
||||
import ContainerNetworkClient
|
||||
import ContainerNetworkServer
|
||||
import ContainerNetworkVmnetServer
|
||||
import ContainerPlugin
|
||||
import ContainerResource
|
||||
import ContainerXPC
|
||||
@@ -99,14 +100,14 @@ extension NetworkVmnetHelper {
|
||||
log: log
|
||||
)
|
||||
try await network.start()
|
||||
let server = try await NetworkService(network: network, log: log)
|
||||
let service = try await DefaultNetworkService(network: network, log: log)
|
||||
let harness = NetworkHarness(service: service)
|
||||
let xpc = XPCServer(
|
||||
identifier: serviceIdentifier,
|
||||
routes: [
|
||||
NetworkRoutes.state.rawValue: XPCServer.route(server.state),
|
||||
NetworkRoutes.allocate.rawValue: server.allocate,
|
||||
NetworkRoutes.lookup.rawValue: XPCServer.route(server.lookup),
|
||||
NetworkRoutes.disableAllocator.rawValue: XPCServer.route(server.disableAllocator),
|
||||
NetworkRoutes.state.rawValue: XPCServer.route(harness.state),
|
||||
NetworkRoutes.allocate.rawValue: harness.allocate,
|
||||
NetworkRoutes.lookup.rawValue: XPCServer.route(harness.lookup),
|
||||
],
|
||||
log: log
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerAPIClient
|
||||
import ContainerNetworkServiceClient
|
||||
import ContainerNetworkClient
|
||||
import ContainerPersistence
|
||||
import ContainerPlugin
|
||||
import ContainerResource
|
||||
@@ -30,7 +30,7 @@ import SystemPackage
|
||||
public actor NetworksService {
|
||||
struct NetworkServiceState {
|
||||
var networkState: NetworkState
|
||||
var client: ContainerNetworkServiceClient.NetworkClient
|
||||
var client: ContainerNetworkClient.NetworkClient
|
||||
}
|
||||
|
||||
private let pluginLoader: PluginLoader
|
||||
@@ -389,7 +389,7 @@ public actor NetworksService {
|
||||
return pluginInfo
|
||||
}
|
||||
|
||||
private static func getClient(configuration: NetworkConfiguration) throws -> ContainerNetworkServiceClient.NetworkClient {
|
||||
private static func getClient(configuration: NetworkConfiguration) throws -> ContainerNetworkClient.NetworkClient {
|
||||
guard let pluginInfo = configuration.pluginInfo else {
|
||||
throw ContainerizationError(.internalError, message: "network \(configuration.id) missing plugin information")
|
||||
}
|
||||
|
||||
-13
@@ -113,15 +113,6 @@ extension NetworkClient {
|
||||
}
|
||||
}
|
||||
|
||||
public func disableAllocator() async throws -> Bool {
|
||||
let request = XPCMessage(route: NetworkRoutes.disableAllocator.rawValue)
|
||||
|
||||
let client = createClient()
|
||||
|
||||
let response = try await client.send(request)
|
||||
return try response.allocatorDisabled()
|
||||
}
|
||||
|
||||
private func createClient() -> XPCClient {
|
||||
XPCClient(service: machServiceLabel)
|
||||
}
|
||||
@@ -135,10 +126,6 @@ extension XPCMessage {
|
||||
return XPCMessage(object: additionalData)
|
||||
}
|
||||
|
||||
public func allocatorDisabled() throws -> Bool {
|
||||
self.bool(key: NetworkKeys.allocatorDisabled.rawValue)
|
||||
}
|
||||
|
||||
public func attachment() throws -> Attachment {
|
||||
let data = self.dataNoCopy(key: NetworkKeys.attachment.rawValue)
|
||||
guard let data else {
|
||||
-1
@@ -16,7 +16,6 @@
|
||||
|
||||
public enum NetworkKeys: String {
|
||||
case additionalData
|
||||
case allocatorDisabled
|
||||
case attachment
|
||||
case hostname
|
||||
case macAddress
|
||||
-2
@@ -19,8 +19,6 @@ public enum NetworkRoutes: String {
|
||||
case state = "com.apple.container.network/state"
|
||||
/// Allocates parameters for attaching a sandbox to the network.
|
||||
case allocate = "com.apple.container.network/allocate"
|
||||
/// Disables the allocator if no sandboxes are attached.
|
||||
case disableAllocator = "com.apple.container.network/disableAllocator"
|
||||
/// Retrieves the allocation for a hostname.
|
||||
case lookup = "com.apple.container.network/lookup"
|
||||
}
|
||||
-5
@@ -52,11 +52,6 @@ actor AttachmentAllocator {
|
||||
return index
|
||||
}
|
||||
|
||||
/// If no addresses are allocated, prevent future allocations and return true.
|
||||
func disableAllocator() async -> Bool {
|
||||
allocator.disableAllocator()
|
||||
}
|
||||
|
||||
/// Retrieve the allocator index for a hostname.
|
||||
func lookup(hostname: String) async throws -> UInt32? {
|
||||
hostnames[hostname]
|
||||
+20
-58
@@ -14,15 +14,13 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerNetworkServiceClient
|
||||
import ContainerResource
|
||||
import ContainerXPC
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
public actor NetworkService: Sendable {
|
||||
public actor DefaultNetworkService: NetworkService {
|
||||
private let network: any Network
|
||||
private let log: Logger
|
||||
private var allocator: AttachmentAllocator
|
||||
@@ -50,15 +48,16 @@ public actor NetworkService: Sendable {
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func state(_ message: XPCMessage) async throws -> XPCMessage {
|
||||
let reply = message.reply()
|
||||
let state = await network.state
|
||||
try reply.setState(state)
|
||||
return reply
|
||||
public func state() async throws -> NetworkState {
|
||||
await network.state
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func allocate(_ message: XPCMessage, _ session: XPCServerSession) async throws -> XPCMessage {
|
||||
public func allocate(
|
||||
hostname: String,
|
||||
macAddress: MACAddress?,
|
||||
session: XPCServerSession
|
||||
) async throws -> (attachment: Attachment, additionalData: XPCMessage?) {
|
||||
log.debug("enter", metadata: ["func": "\(#function)"])
|
||||
defer { log.debug("exit", metadata: ["func": "\(#function)"]) }
|
||||
|
||||
@@ -67,11 +66,7 @@ public actor NetworkService: Sendable {
|
||||
throw ContainerizationError(.invalidState, message: "invalid network state - network \(state.id) must be running")
|
||||
}
|
||||
|
||||
let hostname = try message.hostname()
|
||||
let macAddress =
|
||||
try message.string(key: NetworkKeys.macAddress.rawValue)
|
||||
.map { try MACAddress($0) }
|
||||
?? MACAddress((UInt64.random(in: 0...UInt64.max) & 0x0cff_ffff_ffff) | 0xf200_0000_0000)
|
||||
let macAddress = macAddress ?? MACAddress((UInt64.random(in: 0...UInt64.max) & 0x0cff_ffff_ffff) | 0xf200_0000_0000)
|
||||
let index = try await allocator.allocate(hostname: hostname)
|
||||
let ipv6Address = try status.ipv6Subnet
|
||||
.map { try CIDRv6(macAddress.ipv6Address(network: $0.lower), prefix: $0.prefix) }
|
||||
@@ -93,12 +88,10 @@ public actor NetworkService: Sendable {
|
||||
"ipv6Address": "\(attachment.ipv6Address?.description ?? "unavailable")",
|
||||
"macAddress": "\(attachment.macAddress?.description ?? "unspecified")",
|
||||
])
|
||||
let reply = message.reply()
|
||||
try reply.setAttachment(attachment)
|
||||
|
||||
var additionalData: XPCMessage?
|
||||
try network.withAdditionalData {
|
||||
if let additionalData = $0 {
|
||||
try reply.setAdditionalData(additionalData.underlying)
|
||||
}
|
||||
additionalData = $0
|
||||
}
|
||||
macAddresses[index] = macAddress
|
||||
|
||||
@@ -110,7 +103,7 @@ public actor NetworkService: Sendable {
|
||||
}
|
||||
allocationsBySession[session]!.append((hostname: hostname, index: index))
|
||||
|
||||
return reply
|
||||
return (attachment: attachment, additionalData: additionalData)
|
||||
}
|
||||
|
||||
private func releaseSession(_ session: XPCServerSession) async {
|
||||
@@ -125,7 +118,7 @@ public actor NetworkService: Sendable {
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func lookup(_ message: XPCMessage) async throws -> XPCMessage {
|
||||
public func lookup(hostname: String) async throws -> Attachment? {
|
||||
log.debug("enter", metadata: ["func": "\(#function)"])
|
||||
defer { log.debug("exit", metadata: ["func": "\(#function)"]) }
|
||||
|
||||
@@ -134,15 +127,16 @@ public actor NetworkService: Sendable {
|
||||
throw ContainerizationError(.invalidState, message: "invalid network state - network \(state.id) must be running")
|
||||
}
|
||||
|
||||
let hostname = try message.hostname()
|
||||
// Invariant: hostname -> index if and only if index -> MAC address
|
||||
let index = try await allocator.lookup(hostname: hostname)
|
||||
let reply = message.reply()
|
||||
guard let index else {
|
||||
return reply
|
||||
return nil
|
||||
}
|
||||
guard let macAddress = macAddresses[index] else {
|
||||
return reply
|
||||
return nil
|
||||
}
|
||||
|
||||
// populate attachment
|
||||
let address = IPv4Address(index)
|
||||
let subnet = status.ipv4Subnet
|
||||
let ipv4Address = try CIDRv4(address, prefix: subnet.prefix)
|
||||
@@ -162,39 +156,7 @@ public actor NetworkService: Sendable {
|
||||
"hostname": "\(hostname)",
|
||||
"address": "\(address)",
|
||||
])
|
||||
try reply.setAttachment(attachment)
|
||||
return reply
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func disableAllocator(_ message: XPCMessage) async throws -> XPCMessage {
|
||||
log.debug("enter", metadata: ["func": "\(#function)"])
|
||||
defer { log.debug("exit", metadata: ["func": "\(#function)"]) }
|
||||
|
||||
let success = await allocator.disableAllocator()
|
||||
log.info("attempted allocator disable", metadata: ["success": "\(success)"])
|
||||
let reply = message.reply()
|
||||
reply.setAllocatorDisabled(success)
|
||||
return reply
|
||||
}
|
||||
}
|
||||
|
||||
extension XPCMessage {
|
||||
fileprivate func setAdditionalData(_ additionalData: xpc_object_t) throws {
|
||||
xpc_dictionary_set_value(self.underlying, NetworkKeys.additionalData.rawValue, additionalData)
|
||||
}
|
||||
|
||||
fileprivate func setAllocatorDisabled(_ allocatorDisabled: Bool) {
|
||||
self.set(key: NetworkKeys.allocatorDisabled.rawValue, value: allocatorDisabled)
|
||||
}
|
||||
|
||||
fileprivate func setAttachment(_ attachment: Attachment) throws {
|
||||
let data = try JSONEncoder().encode(attachment)
|
||||
self.set(key: NetworkKeys.attachment.rawValue, value: data)
|
||||
}
|
||||
|
||||
fileprivate func setState(_ state: NetworkState) throws {
|
||||
let data = try JSONEncoder().encode(state)
|
||||
self.set(key: NetworkKeys.state.rawValue, value: data)
|
||||
return attachment
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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
|
||||
import ContainerResource
|
||||
import ContainerXPC
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
|
||||
public actor NetworkHarness: Sendable {
|
||||
private let service: NetworkService
|
||||
|
||||
public init(service: NetworkService) {
|
||||
self.service = service
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func state(_ message: XPCMessage) async throws -> XPCMessage {
|
||||
let reply = message.reply()
|
||||
let state = try await service.state()
|
||||
try reply.setState(state)
|
||||
return reply
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func allocate(_ message: XPCMessage, _ session: XPCServerSession) async throws -> XPCMessage {
|
||||
let hostname = try message.hostname()
|
||||
let macAddress =
|
||||
try message.string(key: NetworkKeys.macAddress.rawValue)
|
||||
.map { try MACAddress($0) }
|
||||
|
||||
let (attachment:attachment, additionalData:additionalData) = try await service.allocate(
|
||||
hostname: hostname,
|
||||
macAddress: macAddress,
|
||||
session: session
|
||||
)
|
||||
|
||||
let reply = message.reply()
|
||||
try reply.setAttachment(attachment)
|
||||
if let additionalData {
|
||||
try reply.setAdditionalData(additionalData.underlying)
|
||||
}
|
||||
|
||||
return reply
|
||||
}
|
||||
|
||||
@Sendable
|
||||
public func lookup(_ message: XPCMessage) async throws -> XPCMessage {
|
||||
let hostname = try message.hostname()
|
||||
let reply = message.reply()
|
||||
guard let attachment = try await service.lookup(hostname: hostname) else {
|
||||
return reply
|
||||
}
|
||||
|
||||
try reply.setAttachment(attachment)
|
||||
return reply
|
||||
}
|
||||
}
|
||||
|
||||
extension XPCMessage {
|
||||
fileprivate func setAdditionalData(_ additionalData: xpc_object_t) throws {
|
||||
xpc_dictionary_set_value(self.underlying, NetworkKeys.additionalData.rawValue, additionalData)
|
||||
}
|
||||
|
||||
fileprivate func setAttachment(_ attachment: Attachment) throws {
|
||||
let data = try JSONEncoder().encode(attachment)
|
||||
self.set(key: NetworkKeys.attachment.rawValue, value: data)
|
||||
}
|
||||
|
||||
fileprivate func setState(_ state: NetworkState) throws {
|
||||
let data = try JSONEncoder().encode(state)
|
||||
self.set(key: NetworkKeys.state.rawValue, value: data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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 ContainerResource
|
||||
import ContainerXPC
|
||||
import ContainerizationExtras
|
||||
|
||||
/// A network service
|
||||
public protocol NetworkService: Sendable {
|
||||
/// Gets the properties of the realized network.
|
||||
func state() async throws -> NetworkState
|
||||
|
||||
/// Register a hostname and allocate associated addresses.
|
||||
func allocate(
|
||||
hostname: String,
|
||||
macAddress: MACAddress?,
|
||||
session: XPCServerSession
|
||||
) async throws -> (attachment: Attachment, additionalData: XPCMessage?)
|
||||
|
||||
/// Return the attachment for a hostname if it is registered with the network.
|
||||
func lookup(hostname: String) async throws -> Attachment?
|
||||
}
|
||||
+1
-1
@@ -14,11 +14,11 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerNetworkServer
|
||||
import ContainerResource
|
||||
import ContainerXPC
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
public actor AllocationOnlyVmnetNetwork: Network {
|
||||
+2
-4
@@ -14,22 +14,20 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerNetworkServer
|
||||
import ContainerResource
|
||||
import ContainerXPC
|
||||
import Containerization
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import Dispatch
|
||||
import Foundation
|
||||
import Logging
|
||||
import Synchronization
|
||||
import SystemConfiguration
|
||||
import XPC
|
||||
import vmnet
|
||||
|
||||
/// Creates a vmnet network with reservation APIs.
|
||||
@available(macOS 26, *)
|
||||
public final class ReservedVmnetNetwork: Network {
|
||||
public final class ReservedVmnetNetwork: ContainerNetworkServer.Network {
|
||||
private struct State {
|
||||
var networkState: NetworkState
|
||||
var network: vmnet_network_ref?
|
||||
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerNetworkServiceClient
|
||||
import ContainerNetworkClient
|
||||
import ContainerOS
|
||||
import ContainerPersistence
|
||||
import ContainerResource
|
||||
@@ -177,7 +177,7 @@ public actor RuntimeService {
|
||||
do {
|
||||
for (index, info) in networkBootstrapInfos.enumerated() {
|
||||
let attachmentConfig = config.networks[index]
|
||||
let client = ContainerNetworkServiceClient.NetworkClient(id: attachmentConfig.network, plugin: info.pluginInfo.plugin)
|
||||
let client = ContainerNetworkClient.NetworkClient(id: attachmentConfig.network, plugin: info.pluginInfo.plugin)
|
||||
let session = client.connect()
|
||||
sessions.append(session)
|
||||
var (attachment, additionalData) = try await client.allocate(
|
||||
|
||||
Reference in New Issue
Block a user