Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ let package = Package(
.product(name: "ContainerizationExtras", package: "containerization"),
.product(name: "ContainerizationOS", package: "containerization"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"ContainerAPIClient",
"ContainerNetworkServiceClient",
"ContainerOS",
"ContainerPersistence",
"ContainerResource",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ import SystemPackage
public actor ContainersService {
struct ContainerState {
var snapshot: ContainerSnapshot
var client: SandboxClient?
var allocatedAttachments: [AllocatedAttachment]
var networkSessions: [XPCClientSession]
var client: SandboxClient? = nil

func getClient() throws -> SandboxClient {
guard let client else {
Expand Down Expand Up @@ -133,8 +131,6 @@ public actor ContainersService {
networks: [],
startedDate: nil
),
allocatedAttachments: [],
networkSessions: []
)
results[config.id] = state
guard runtimePlugins.first(where: { $0.name == config.runtimeHandler }) != nil else {
Expand Down Expand Up @@ -396,7 +392,7 @@ public actor ContainersService {
networks: [],
startedDate: nil
)
await self.setContainerState(configuration.id, ContainerState(snapshot: snapshot, allocatedAttachments: [], networkSessions: []), context: context)
await self.setContainerState(configuration.id, ContainerState(snapshot: snapshot), context: context)
} catch {
throw error
}
Expand Down Expand Up @@ -436,41 +432,15 @@ public actor ContainersService {
let path = self.containerRoot.appendingPathComponent(id)
let (config, _) = try Self.getContainerConfiguration(at: path)

var allocatedAttachments = [AllocatedAttachment]()
var networkSessions = [XPCClientSession]()
do {
for n in config.networks {
guard
let (allocatedAttach, session) = try await self.networksService?.allocate(
id: n.network,
hostname: n.options.hostname,
macAddress: n.options.macAddress
)
else {
throw ContainerizationError(.internalError, message: "failed to allocate a network")
}

var finalAttach = allocatedAttach
if let mtu = n.options.mtu {
let a = allocatedAttach.attachment
finalAttach = AllocatedAttachment(
attachment: Attachment(
network: a.network,
hostname: a.hostname,
ipv4Address: a.ipv4Address,
ipv4Gateway: a.ipv4Gateway,
ipv6Address: a.ipv6Address,
macAddress: a.macAddress,
mtu: mtu
),
additionalData: allocatedAttach.additionalData,
pluginInfo: allocatedAttach.pluginInfo
)
}
allocatedAttachments.append(finalAttach)
networkSessions.append(session)
var networkBootstrapInfos = [NetworkBootstrapInfo]()
for n in config.networks {
guard let pluginInfo = try await self.networksService?.pluginInfo(id: n.network) else {
throw ContainerizationError(.internalError, message: "failed to get plugin info for network \(n.network)")
}
networkBootstrapInfos.append(NetworkBootstrapInfo(pluginInfo: pluginInfo))
}

do {
try Self.registerService(
plugin: self.runtimePlugins.first { $0.name == config.runtimeHandler }!,
loader: self.pluginLoader,
Expand All @@ -484,22 +454,16 @@ public actor ContainersService {
id: id,
runtime: runtime
)
try await sandboxClient.bootstrap(stdio: stdio, allocatedAttachments: allocatedAttachments, dynamicEnv: dynamicEnv)
try await sandboxClient.bootstrap(stdio: stdio, networkBootstrapInfos: networkBootstrapInfos, dynamicEnv: dynamicEnv)

try await self.exitMonitor.registerProcess(
id: id,
onExit: self.handleContainerExit
)

state.client = sandboxClient
state.allocatedAttachments = allocatedAttachments
state.networkSessions = networkSessions
await self.setContainerState(id, state, context: context)
} catch {
for session in networkSessions {
session.close()
}

let label = Self.fullLaunchdServiceLabel(
runtimeName: config.runtimeHandler,
instanceId: id
Expand Down Expand Up @@ -994,17 +958,9 @@ public actor ContainersService {
])
}

// Close network sessions — the network helper auto-releases allocations on disconnect.
self.log.info("closing network sessions", metadata: ["id": "\(id)"])
for session in state.networkSessions {
session.close()
}

state.snapshot.status = .stopped
state.snapshot.networks = []
state.client = nil
state.allocatedAttachments = []
state.networkSessions = []
await self.setContainerState(id, state, context: context)

let options = try getContainerCreationOptions(id: id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import ContainerNetworkServiceClient
import ContainerPersistence
import ContainerPlugin
import ContainerResource
import ContainerXPC
import Containerization
import ContainerizationError
import ContainerizationExtras
Expand Down Expand Up @@ -380,26 +379,14 @@ public actor NetworksService {
}
}

public func allocate(id: String, hostname: String, macAddress: MACAddress?) async throws -> (AllocatedAttachment, XPCClientSession) {
public func pluginInfo(id: String) throws -> NetworkPluginInfo {
guard let serviceState = serviceStates[id] else {
throw ContainerizationError(.notFound, message: "no network for id \(id)")
}
guard let pluginInfo = serviceState.networkState.pluginInfo else {
throw ContainerizationError(.internalError, message: "network \(id) missing plugin information")
}
let session = serviceState.client.connect()
do {
let (attach, additionalData) = try await serviceState.client.allocate(hostname: hostname, macAddress: macAddress, on: session)
let alloc = AllocatedAttachment(
attachment: attach,
additionalData: additionalData,
pluginInfo: pluginInfo
)
return (alloc, session)
} catch {
session.close()
throw error
}
return pluginInfo
}

private static func getClient(configuration: NetworkConfiguration) throws -> ContainerNetworkServiceClient.NetworkClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public actor NetworkService: Sendable {
private let log: Logger
private var allocator: AttachmentAllocator
private var macAddresses: [UInt32: MACAddress]
private var allocationsBySession: [XPCServerSession: [(hostname: String, index: UInt32)]] = [:]
private var allocationsBySession: [XPCServerSession: [(hostname: String, index: UInt32)]]

/// Set up a network service for the specified network.
public init(
Expand All @@ -42,10 +42,11 @@ public actor NetworkService: Sendable {
let subnet = status.ipv4Subnet

let size = Int(subnet.upper.value - subnet.lower.value - 3)
self.allocator = try AttachmentAllocator(lower: subnet.lower.value + 2, size: size)
self.macAddresses = [:]
self.network = network
self.log = log
self.allocator = try AttachmentAllocator(lower: subnet.lower.value + 2, size: size)
self.macAddresses = [:]
self.allocationsBySession = [:]
}

@Sendable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,16 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ContainerXPC
import ContainerResource

/// AllocatedAttachment represents a network attachment that has been allocated for use
/// by a container and any additional relevant data needed for a sandbox to properly
/// configure networking on container bootstrap.
public struct AllocatedAttachment: Sendable {
public let attachment: Attachment
public let additionalData: XPCMessage?
/// 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.
public struct NetworkBootstrapInfo: Codable, Sendable {
/// Plugin info identifying which network helper to contact and which interface
/// strategy the runtime should use.
public let pluginInfo: NetworkPluginInfo

public init(attachment: Attachment, additionalData: XPCMessage?, pluginInfo: NetworkPluginInfo) {
self.attachment = attachment
self.additionalData = additionalData
public init(pluginInfo: NetworkPluginInfo) {
self.pluginInfo = pluginInfo
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public struct SandboxClient: Sendable {
extension SandboxClient {
public func bootstrap(
stdio: [FileHandle?],
allocatedAttachments: [AllocatedAttachment],
networkBootstrapInfos: [NetworkBootstrapInfo],
dynamicEnv: [String: String] = [:]
) async throws {
let request = XPCMessage(route: SandboxRoutes.bootstrap.rawValue)
Expand All @@ -104,7 +104,8 @@ extension SandboxClient {
let dynamicEnv = try JSONEncoder().encode(dynamicEnv)
request.set(key: SandboxKeys.dynamicEnv.rawValue, value: dynamicEnv)

try request.setAllocatedAttachments(allocatedAttachments)
let infosData = try JSONEncoder().encode(networkBootstrapInfos)
request.set(key: SandboxKeys.networkBootstrapInfos.rawValue, value: infosData)
try await self.client.send(request)
} catch {
throw ContainerizationError(
Expand Down Expand Up @@ -331,25 +332,10 @@ extension XPCMessage {
return try JSONDecoder().decode(SandboxSnapshot.self, from: data)
}

func setAllocatedAttachments(_ allocatedAttachments: [AllocatedAttachment]) throws {
let encoder = JSONEncoder()
let allocatedAttachmentsArray = xpc_array_create_empty()
for allocatedAttach in allocatedAttachments {
let xpcObject: xpc_object_t = xpc_dictionary_create_empty()
let networkXPC = XPCMessage(object: xpcObject)

let attachmentEncoded = try encoder.encode(allocatedAttach.attachment)
networkXPC.set(key: SandboxKeys.networkAttachment.rawValue, value: attachmentEncoded)

let pluginInfoEncoded = try encoder.encode(allocatedAttach.pluginInfo)
networkXPC.set(key: SandboxKeys.networkPluginInfo.rawValue, value: pluginInfoEncoded)

if let additionalData = allocatedAttach.additionalData {
xpc_dictionary_set_value(networkXPC.underlying, SandboxKeys.networkAdditionalData.rawValue, additionalData.underlying)
}

xpc_array_append_value(allocatedAttachmentsArray, networkXPC.underlying)
public func networkBootstrapInfos() throws -> [NetworkBootstrapInfo] {
guard let data = self.dataNoCopy(key: SandboxKeys.networkBootstrapInfos.rawValue) else {
throw ContainerizationError(.invalidArgument, message: "missing networkBootstrapInfos in bootstrap message")
}
self.set(key: SandboxKeys.allocatedAttachments.rawValue, value: allocatedAttachmentsArray)
return try JSONDecoder().decode([NetworkBootstrapInfo].self, from: data)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ public enum SandboxKeys: String {
/// Special-case environment variables recomputed on each container start
case dynamicEnv

/// Network resource keys.
case allocatedAttachments
case networkAdditionalData
case networkAttachment
case networkPluginInfo
/// Per-network connection info passed to the runtime so it can allocate directly.
case networkBootstrapInfos
}
Loading