From 223bb7ea382bcfb6981abe7c6feb6296b1c898e4 Mon Sep 17 00:00:00 2001 From: John Logan Date: Thu, 28 May 2026 13:34:13 -0700 Subject: [PATCH 1/2] Reorganize Swift package targets for network plugin. - 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. --- Package.resolved | 6 +- Package.swift | 52 ++++++----- Sources/APIServer/APIServer+Start.swift | 1 - .../NetworkVmnetHelper+Start.swift | 15 ++-- .../Server/Networks/NetworksService.swift | 6 +- .../Client/NetworkClient.swift | 13 --- .../Client/NetworkKeys.swift | 1 - .../Client/NetworkRoutes.swift | 2 - .../Server/AttachmentAllocator.swift | 5 -- .../Server/DefaultNetworkService.swift} | 78 +++++------------ .../Server/Network.swift | 0 .../Network/Server/NetworkHarness.swift | 87 +++++++++++++++++++ .../Network/Server/NetworkService.swift | 35 ++++++++ .../Server/AllocationOnlyVmnetNetwork.swift | 2 +- .../Server/ReservedVmnetNetwork.swift | 6 +- .../RuntimeLinux/Server/RuntimeService.swift | 4 +- .../AttachmentAllocatorTest.swift | 51 +---------- 17 files changed, 191 insertions(+), 173 deletions(-) rename Sources/Services/{ContainerNetworkService => Network}/Client/NetworkClient.swift (93%) rename Sources/Services/{ContainerNetworkService => Network}/Client/NetworkKeys.swift (97%) rename Sources/Services/{ContainerNetworkService => Network}/Client/NetworkRoutes.swift (89%) rename Sources/Services/{ContainerNetworkService => Network}/Server/AttachmentAllocator.swift (92%) rename Sources/Services/{ContainerNetworkService/Server/NetworkService.swift => Network/Server/DefaultNetworkService.swift} (69%) rename Sources/Services/{ContainerNetworkService => Network}/Server/Network.swift (100%) create mode 100644 Sources/Services/Network/Server/NetworkHarness.swift create mode 100644 Sources/Services/Network/Server/NetworkService.swift rename Sources/Services/{ContainerNetworkService => NetworkVmnet}/Server/AllocationOnlyVmnetNetwork.swift (99%) rename Sources/Services/{ContainerNetworkService => NetworkVmnet}/Server/ReservedVmnetNetwork.swift (98%) rename Tests/{ContainerNetworkServiceTests => ContainerNetworkServerTests}/AttachmentAllocatorTest.swift (78%) diff --git a/Package.resolved b/Package.resolved index 6d19fb55d..f0a33d439 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "d9d032f8b1ad94cf9006bd4b441ce1dad52802b04b1d7f7b30baedd32cee0e8b", + "originHash" : "f3e6e0a1ee627e1f396b1565e72a50b179394e7011667ec4569dc53455ee06ed", "pins" : [ { "identity" : "async-http-client", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/containerization.git", "state" : { - "revision" : "9205a766302a18e06a0ce43f8b7e483625e3e50f", - "version" : "0.33.1" + "revision" : "2550dd49f1890702f6fe0171212050bbce9d3825", + "version" : "0.33.2" } }, { diff --git a/Package.swift b/Package.swift index 2cc2e7092..adad7a5e7 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ import PackageDescription let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0" let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified" let builderShimVersion = "0.12.0" -let scVersion = "0.33.1" +let scVersion = "0.33.2" let package = Package( name: "container", @@ -34,7 +34,9 @@ let package = Package( .library(name: "ContainerAPIService", targets: ["ContainerAPIService"]), .library(name: "ContainerAPIClient", targets: ["ContainerAPIClient"]), .library(name: "ContainerImagesService", targets: ["ContainerImagesService", "ContainerImagesServiceClient"]), - .library(name: "ContainerNetworkService", targets: ["ContainerNetworkService", "ContainerNetworkServiceClient"]), + .library(name: "ContainerNetworkClient", targets: ["ContainerNetworkClient"]), + .library(name: "ContainerNetworkServer", targets: ["ContainerNetworkServer"]), + .library(name: "ContainerNetworkVmnetServer", targets: ["ContainerNetworkVmnetServer"]), .library(name: "ContainerResource", targets: ["ContainerResource"]), .library(name: "ContainerLog", targets: ["ContainerLog"]), .library(name: "ContainerPersistence", targets: ["ContainerPersistence"]), @@ -106,7 +108,6 @@ let package = Package( "ContainerBuild", "ContainerAPIClient", "ContainerLog", - "ContainerNetworkService", "ContainerPersistence", "ContainerPlugin", "ContainerResource", @@ -166,7 +167,7 @@ let package = Package( "ContainerAPIService", "ContainerAPIClient", "ContainerLog", - "ContainerNetworkService", + "ContainerNetworkClient", "ContainerPersistence", "ContainerPlugin", "ContainerResource", @@ -188,7 +189,7 @@ let package = Package( .product(name: "SystemPackage", package: "swift-system"), "CVersion", "ContainerAPIClient", - "ContainerNetworkServiceClient", + "ContainerNetworkClient", "ContainerPersistence", "ContainerPlugin", "ContainerResource", @@ -291,13 +292,12 @@ let package = Package( dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Logging", package: "swift-log"), - .product(name: "Containerization", package: "containerization"), .product(name: "ContainerizationExtras", package: "containerization"), - .product(name: "ContainerizationIO", package: "containerization"), .product(name: "ContainerizationOS", package: "containerization"), "ContainerLog", - "ContainerNetworkService", - "ContainerNetworkServiceClient", + "ContainerNetworkClient", + "ContainerNetworkServer", + "ContainerNetworkVmnetServer", "ContainerPersistence", "ContainerPlugin", "ContainerResource", @@ -308,36 +308,42 @@ let package = Package( exclude: ["config.toml"] ), .target( - name: "ContainerNetworkService", + name: "ContainerNetworkClient", + dependencies: [ + .product(name: "ContainerizationExtras", package: "containerization"), + "ContainerResource", + "ContainerXPC", + ], + path: "Sources/Services/Network/Client" + ), + .target( + name: "ContainerNetworkServer", dependencies: [ .product(name: "Logging", package: "swift-log"), - .product(name: "Containerization", package: "containerization"), - .product(name: "ContainerizationOS", package: "containerization"), - "ContainerNetworkServiceClient", - "ContainerPersistence", + .product(name: "ContainerizationExtras", package: "containerization"), + "ContainerNetworkClient", "ContainerResource", "ContainerXPC", ], - path: "Sources/Services/ContainerNetworkService/Server" + path: "Sources/Services/Network/Server" ), .testTarget( - name: "ContainerNetworkServiceTests", + name: "ContainerNetworkServerTests", dependencies: [ - .product(name: "Containerization", package: "containerization"), .product(name: "ContainerizationExtras", package: "containerization"), - "ContainerNetworkService", + "ContainerNetworkServer", ] ), .target( - name: "ContainerNetworkServiceClient", + name: "ContainerNetworkVmnetServer", dependencies: [ .product(name: "Logging", package: "swift-log"), - .product(name: "Containerization", package: "containerization"), - "ContainerLog", + .product(name: "ContainerizationExtras", package: "containerization"), + "ContainerNetworkServer", "ContainerResource", "ContainerXPC", ], - path: "Sources/Services/ContainerNetworkService/Client" + path: "Sources/Services/NetworkVmnet/Server" ), .target( name: "ContainerRuntimeLinuxClient", @@ -371,7 +377,7 @@ let package = Package( .product(name: "ContainerizationOS", package: "containerization"), .product(name: "ArgumentParser", package: "swift-argument-parser"), "ContainerAPIClient", - "ContainerNetworkServiceClient", + "ContainerNetworkClient", "ContainerOS", "ContainerPersistence", "ContainerResource", diff --git a/Sources/APIServer/APIServer+Start.swift b/Sources/APIServer/APIServer+Start.swift index ada055aae..e6180e575 100644 --- a/Sources/APIServer/APIServer+Start.swift +++ b/Sources/APIServer/APIServer+Start.swift @@ -18,7 +18,6 @@ import ArgumentParser import ContainerAPIClient import ContainerAPIService import ContainerLog -import ContainerNetworkService import ContainerPersistence import ContainerPlugin import ContainerResource diff --git a/Sources/Plugins/NetworkVmnet/NetworkVmnetHelper+Start.swift b/Sources/Plugins/NetworkVmnet/NetworkVmnetHelper+Start.swift index 2cc52c555..2dadd428b 100644 --- a/Sources/Plugins/NetworkVmnet/NetworkVmnetHelper+Start.swift +++ b/Sources/Plugins/NetworkVmnet/NetworkVmnetHelper+Start.swift @@ -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 ) diff --git a/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift b/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift index 271f07194..512194f2d 100644 --- a/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift +++ b/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift @@ -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") } diff --git a/Sources/Services/ContainerNetworkService/Client/NetworkClient.swift b/Sources/Services/Network/Client/NetworkClient.swift similarity index 93% rename from Sources/Services/ContainerNetworkService/Client/NetworkClient.swift rename to Sources/Services/Network/Client/NetworkClient.swift index b7a0e8bb1..18b6591ca 100644 --- a/Sources/Services/ContainerNetworkService/Client/NetworkClient.swift +++ b/Sources/Services/Network/Client/NetworkClient.swift @@ -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 { diff --git a/Sources/Services/ContainerNetworkService/Client/NetworkKeys.swift b/Sources/Services/Network/Client/NetworkKeys.swift similarity index 97% rename from Sources/Services/ContainerNetworkService/Client/NetworkKeys.swift rename to Sources/Services/Network/Client/NetworkKeys.swift index cc7715a30..af10bdae7 100644 --- a/Sources/Services/ContainerNetworkService/Client/NetworkKeys.swift +++ b/Sources/Services/Network/Client/NetworkKeys.swift @@ -16,7 +16,6 @@ public enum NetworkKeys: String { case additionalData - case allocatorDisabled case attachment case hostname case macAddress diff --git a/Sources/Services/ContainerNetworkService/Client/NetworkRoutes.swift b/Sources/Services/Network/Client/NetworkRoutes.swift similarity index 89% rename from Sources/Services/ContainerNetworkService/Client/NetworkRoutes.swift rename to Sources/Services/Network/Client/NetworkRoutes.swift index d3f005498..6e3b91fe4 100644 --- a/Sources/Services/ContainerNetworkService/Client/NetworkRoutes.swift +++ b/Sources/Services/Network/Client/NetworkRoutes.swift @@ -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" } diff --git a/Sources/Services/ContainerNetworkService/Server/AttachmentAllocator.swift b/Sources/Services/Network/Server/AttachmentAllocator.swift similarity index 92% rename from Sources/Services/ContainerNetworkService/Server/AttachmentAllocator.swift rename to Sources/Services/Network/Server/AttachmentAllocator.swift index fb1f537c3..b7d3aeebb 100644 --- a/Sources/Services/ContainerNetworkService/Server/AttachmentAllocator.swift +++ b/Sources/Services/Network/Server/AttachmentAllocator.swift @@ -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] diff --git a/Sources/Services/ContainerNetworkService/Server/NetworkService.swift b/Sources/Services/Network/Server/DefaultNetworkService.swift similarity index 69% rename from Sources/Services/ContainerNetworkService/Server/NetworkService.swift rename to Sources/Services/Network/Server/DefaultNetworkService.swift index 8d12ae76c..43990a662 100644 --- a/Sources/Services/ContainerNetworkService/Server/NetworkService.swift +++ b/Sources/Services/Network/Server/DefaultNetworkService.swift @@ -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 } } diff --git a/Sources/Services/ContainerNetworkService/Server/Network.swift b/Sources/Services/Network/Server/Network.swift similarity index 100% rename from Sources/Services/ContainerNetworkService/Server/Network.swift rename to Sources/Services/Network/Server/Network.swift diff --git a/Sources/Services/Network/Server/NetworkHarness.swift b/Sources/Services/Network/Server/NetworkHarness.swift new file mode 100644 index 000000000..28023de6d --- /dev/null +++ b/Sources/Services/Network/Server/NetworkHarness.swift @@ -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) + } +} diff --git a/Sources/Services/Network/Server/NetworkService.swift b/Sources/Services/Network/Server/NetworkService.swift new file mode 100644 index 000000000..3644fc838 --- /dev/null +++ b/Sources/Services/Network/Server/NetworkService.swift @@ -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? +} diff --git a/Sources/Services/ContainerNetworkService/Server/AllocationOnlyVmnetNetwork.swift b/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift similarity index 99% rename from Sources/Services/ContainerNetworkService/Server/AllocationOnlyVmnetNetwork.swift rename to Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift index d63e48511..c17e60242 100644 --- a/Sources/Services/ContainerNetworkService/Server/AllocationOnlyVmnetNetwork.swift +++ b/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift @@ -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 { diff --git a/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift b/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift similarity index 98% rename from Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift rename to Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift index 6b7977cc5..43a325823 100644 --- a/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift +++ b/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift @@ -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? diff --git a/Sources/Services/RuntimeLinux/Server/RuntimeService.swift b/Sources/Services/RuntimeLinux/Server/RuntimeService.swift index 187de7af6..27ae89cb4 100644 --- a/Sources/Services/RuntimeLinux/Server/RuntimeService.swift +++ b/Sources/Services/RuntimeLinux/Server/RuntimeService.swift @@ -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( diff --git a/Tests/ContainerNetworkServiceTests/AttachmentAllocatorTest.swift b/Tests/ContainerNetworkServerTests/AttachmentAllocatorTest.swift similarity index 78% rename from Tests/ContainerNetworkServiceTests/AttachmentAllocatorTest.swift rename to Tests/ContainerNetworkServerTests/AttachmentAllocatorTest.swift index 9db889763..86ea3eff0 100644 --- a/Tests/ContainerNetworkServiceTests/AttachmentAllocatorTest.swift +++ b/Tests/ContainerNetworkServerTests/AttachmentAllocatorTest.swift @@ -14,11 +14,9 @@ // limitations under the License. //===----------------------------------------------------------------------===// -import ContainerizationError -import ContainerizationExtras import Testing -@testable import ContainerNetworkService +@testable import ContainerNetworkServer struct AttachmentAllocatorTest { @Test func testAllocateSingleHostname() async throws { @@ -145,53 +143,6 @@ struct AttachmentAllocatorTest { #expect(finalAddress4 == newAddress) } - @Test func testDisableAllocatorWhenEmpty() async throws { - let allocator = try AttachmentAllocator(lower: 100, size: 10) - - let disabled = await allocator.disableAllocator() - - #expect(disabled == true) - - // After disabling, allocation should fail - await #expect(throws: Error.self) { - try await allocator.allocate(hostname: "test-host") - } - } - - @Test func testDisableAllocatorWhenNotEmpty() async throws { - let allocator = try AttachmentAllocator(lower: 100, size: 10) - - _ = try await allocator.allocate(hostname: "test-host") - - let disabled = await allocator.disableAllocator() - - #expect(disabled == false) - - // Since disable failed, should still be able to allocate - let address = try await allocator.allocate(hostname: "another-host") - #expect(address >= 100) - #expect(address < 110) - } - - @Test func testDisableAfterDeallocatingAll() async throws { - let allocator = try AttachmentAllocator(lower: 100, size: 10) - - _ = try await allocator.allocate(hostname: "host1") - _ = try await allocator.allocate(hostname: "host2") - - try await allocator.deallocate(hostname: "host1") - try await allocator.deallocate(hostname: "host2") - - let disabled = await allocator.disableAllocator() - - #expect(disabled == true) - - // After disabling, allocation should fail - await #expect(throws: Error.self) { - try await allocator.allocate(hostname: "test-host") - } - } - @Test func testMultipleDeallocationsOfSameHostname() async throws { let allocator = try AttachmentAllocator(lower: 100, size: 10) From cfde2ec00f6756bcb1d24b3395935d9ab527428d Mon Sep 17 00:00:00 2001 From: John Logan Date: Thu, 28 May 2026 14:12:34 -0700 Subject: [PATCH 2/2] Update docc script for new targets. --- scripts/make-docs.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/make-docs.sh b/scripts/make-docs.sh index d904eb0d4..9f1e594c4 100755 --- a/scripts/make-docs.sh +++ b/scripts/make-docs.sh @@ -21,8 +21,9 @@ opts+=("--target" "ContainerAPIClient") opts+=("--target" "ContainerRuntimeClient") opts+=("--target" "ContainerRuntimeLinuxClient") opts+=("--target" "ContainerRuntimeLinuxServer") -opts+=("--target" "ContainerNetworkService") -opts+=("--target" "ContainerNetworkServiceClient") +opts+=("--target" "ContainerNetworkClient") +opts+=("--target" "ContainerNetworkServer") +opts+=("--target" "ContainerNetworkVmnetServer") opts+=("--target" "ContainerImagesService") opts+=("--target" "ContainerImagesServiceClient") opts+=("--target" "ContainerResource")