diff --git a/Sources/ContainerResource/Network/Attachment.swift b/Sources/ContainerResource/Network/Attachment.swift index bbadad75..a6351ab3 100644 --- a/Sources/ContainerResource/Network/Attachment.swift +++ b/Sources/ContainerResource/Network/Attachment.swift @@ -33,6 +33,8 @@ public struct Attachment: Codable, Sendable { public let macAddress: MACAddress? /// The MTU for the network interface. public let mtu: UInt32? + /// The network plugin variant, used by the runtime to select an interface strategy. + public let variant: String? public init( network: String, @@ -41,7 +43,8 @@ public struct Attachment: Codable, Sendable { ipv4Gateway: IPv4Address, ipv6Address: CIDRv6?, macAddress: MACAddress?, - mtu: UInt32? = nil + mtu: UInt32? = nil, + variant: String? = nil ) { self.network = network self.hostname = hostname @@ -50,6 +53,7 @@ public struct Attachment: Codable, Sendable { self.ipv6Address = ipv6Address self.macAddress = macAddress self.mtu = mtu + self.variant = variant } enum CodingKeys: String, CodingKey { @@ -60,6 +64,7 @@ public struct Attachment: Codable, Sendable { case ipv6Address case macAddress case mtu + case variant // TODO: retain for deserialization compatibility for now, remove later case address case gateway @@ -85,6 +90,7 @@ public struct Attachment: Codable, Sendable { ipv6Address = try container.decodeIfPresent(CIDRv6.self, forKey: .ipv6Address) macAddress = try container.decodeIfPresent(MACAddress.self, forKey: .macAddress) mtu = try container.decodeIfPresent(UInt32.self, forKey: .mtu) + variant = try container.decodeIfPresent(String.self, forKey: .variant) } /// Encode the configuration to the supplied Encoder. @@ -98,5 +104,6 @@ public struct Attachment: Codable, Sendable { try container.encodeIfPresent(ipv6Address, forKey: .ipv6Address) try container.encodeIfPresent(macAddress, forKey: .macAddress) try container.encodeIfPresent(mtu, forKey: .mtu) + try container.encodeIfPresent(variant, forKey: .variant) } } diff --git a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift index a37ad691..41f33d49 100644 --- a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift +++ b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift @@ -422,10 +422,10 @@ public actor ContainersService { var networkBootstrapInfos = [NetworkBootstrapInfo]() for n in config.networks { - guard let (plugin, options) = try await self.networksService?.pluginConfiguration(id: n.network) else { - throw ContainerizationError(.internalError, message: "failed to get plugin configuration for network \(n.network)") + guard let plugin = try await self.networksService?.plugin(for: n.network) else { + throw ContainerizationError(.internalError, message: "failed to get plugin for network \(n.network)") } - networkBootstrapInfos.append(NetworkBootstrapInfo(plugin: plugin, options: options)) + networkBootstrapInfos.append(NetworkBootstrapInfo(plugin: plugin)) } do { diff --git a/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift b/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift index 6be54acb..7fe35fae 100644 --- a/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift +++ b/Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift @@ -314,19 +314,11 @@ public actor NetworksService { } } - public func pluginConfiguration(id: String) throws -> (plugin: String, options: [String: String]) { + public func plugin(for id: String) throws -> String { guard let serviceState = serviceStates[id] else { throw ContainerizationError(.notFound, message: "no network for id \(id)") } - var options = serviceState.configuration.options - if options["variant"] == nil { - if #available(macOS 26, *) { - options["variant"] = "reserved" - } else { - options["variant"] = "allocationOnly" - } - } - return (plugin: serviceState.configuration.plugin, options: options) + return serviceState.configuration.plugin } private static func getClient(configuration: NetworkConfiguration) throws -> ContainerNetworkClient.NetworkClient { diff --git a/Sources/Services/Network/Server/DefaultNetworkService.swift b/Sources/Services/Network/Server/DefaultNetworkService.swift index 01f60456..70d17d39 100644 --- a/Sources/Services/Network/Server/DefaultNetworkService.swift +++ b/Sources/Services/Network/Server/DefaultNetworkService.swift @@ -77,7 +77,8 @@ public actor DefaultNetworkService: NetworkService { ipv4Address: try CIDRv4(ip, prefix: status.ipv4Subnet.prefix), ipv4Gateway: status.ipv4Gateway, ipv6Address: ipv6Address, - macAddress: macAddress + macAddress: macAddress, + variant: network.variant ) log.info( "allocated attachment", @@ -146,7 +147,8 @@ public actor DefaultNetworkService: NetworkService { ipv4Address: ipv4Address, ipv4Gateway: status.ipv4Gateway, ipv6Address: ipv6Address, - macAddress: macAddress + macAddress: macAddress, + variant: network.variant ) log.debug( "lookup attachment", diff --git a/Sources/Services/Network/Server/Network.swift b/Sources/Services/Network/Server/Network.swift index aca64d04..6f1ff18e 100644 --- a/Sources/Services/Network/Server/Network.swift +++ b/Sources/Services/Network/Server/Network.swift @@ -22,6 +22,12 @@ public protocol Network: Sendable { /// The network's identifier. var id: String { get } + /// An operational hint passed back to the runtime in the allocate response. + /// Together with the plugin name, the runtime uses this to select the appropriate + /// interface strategy for the sandbox. A `nil` value indicates that the plugin + /// has only a single, default variant. + nonisolated var variant: String? { get } + /// The network's runtime status. `nil` before ``start()`` completes. var status: NetworkStatus? { get async } diff --git a/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift b/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift index 321baf1e..131e8af2 100644 --- a/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift +++ b/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift @@ -50,6 +50,8 @@ public actor AllocationOnlyVmnetNetwork: Network { public nonisolated var id: String { configuration.id } + public nonisolated var variant: String? { "allocationOnly" } + public var status: NetworkStatus? { _status } public nonisolated func withAdditionalData(_ handler: (XPCMessage?) throws -> Void) throws { diff --git a/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift b/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift index c1194242..5b0fee6a 100644 --- a/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift +++ b/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift @@ -63,6 +63,8 @@ public final class ReservedVmnetNetwork: ContainerNetworkServer.Network { public nonisolated var id: String { configuration.id } + public nonisolated var variant: String? { "reserved" } + public var status: NetworkStatus? { stateMutex.withLock { $0.status } } diff --git a/Sources/Services/Runtime/RuntimeClient/NetworkBootstrapInfo.swift b/Sources/Services/Runtime/RuntimeClient/NetworkBootstrapInfo.swift index f4461b82..7f44f05e 100644 --- a/Sources/Services/Runtime/RuntimeClient/NetworkBootstrapInfo.swift +++ b/Sources/Services/Runtime/RuntimeClient/NetworkBootstrapInfo.swift @@ -17,16 +17,12 @@ import ContainerResource /// Plugin info passed from the API server in the sandbox bootstrap message so the -/// runtime can connect to the correct network helper and configure the interface. +/// runtime can connect to the correct network helper. public struct NetworkBootstrapInfo: Codable, Sendable { /// The network plugin name identifying which network helper to contact. public let plugin: String - /// Plugin-specific options, including `variant` which selects the interface strategy. - public let options: [String: String] - - public init(plugin: String, options: [String: String] = [:]) { + public init(plugin: String) { self.plugin = plugin - self.options = options } } diff --git a/Sources/Services/RuntimeLinux/Server/RuntimeService.swift b/Sources/Services/RuntimeLinux/Server/RuntimeService.swift index 6989a982..2320ff45 100644 --- a/Sources/Services/RuntimeLinux/Server/RuntimeService.swift +++ b/Sources/Services/RuntimeLinux/Server/RuntimeService.swift @@ -193,13 +193,14 @@ public actor RuntimeService { ipv4Gateway: attachment.ipv4Gateway, ipv6Address: attachment.ipv6Address, macAddress: attachment.macAddress, - mtu: mtu + mtu: mtu, + variant: attachment.variant ) } - guard let iStrategy = self.interfaceStrategies[NetworkInterfaceKey(plugin: info.plugin, variant: info.options["variant"])] else { + guard let iStrategy = self.interfaceStrategies[NetworkInterfaceKey(plugin: info.plugin, variant: attachment.variant)] else { throw ContainerizationError( .internalError, - message: "no available interface strategy for network \(attachment.network), plugin=\(info.plugin) variant=\(info.options["variant"] ?? "nil")") + message: "no available interface strategy for network \(attachment.network), plugin=\(info.plugin) variant=\(attachment.variant ?? "nil")") } let interface = try iStrategy.toInterface( attachment: attachment,