Skip to content
Open
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
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ let package = Package(
],
path: "Sources/Services/NetworkVmnet/Server"
),
.testTarget(
name: "ContainerNetworkVmnetServerTests",
dependencies: [
"ContainerNetworkVmnetServer"
]
),
.target(
name: "ContainerRuntimeLinuxClient",
dependencies: [],
Expand Down
27 changes: 27 additions & 0 deletions Sources/Services/Network/Server/DefaultNetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public actor DefaultNetworkService: NetworkService {
private let network: any Network
private let log: Logger
private var allocator: AttachmentAllocator
private var allocatorSubnet: CIDRv4
private var macAddresses: [UInt32: MACAddress]
private var allocationsBySession: [XPCServerSession: [(hostname: String, index: UInt32)]]

Expand All @@ -41,12 +42,14 @@ public actor DefaultNetworkService: NetworkService {
self.network = network
self.log = log
self.allocator = try AttachmentAllocator(lower: subnet.lower.value + 2, size: size)
self.allocatorSubnet = subnet
self.macAddresses = [:]
self.allocationsBySession = [:]
}

@Sendable
public func status() async throws -> NetworkStatus {
try refreshNetworkStateIfNeeded()
guard let status = await network.status else {
throw ContainerizationError(.invalidState, message: "network \(network.id) is not running")
}
Expand All @@ -62,9 +65,11 @@ public actor DefaultNetworkService: NetworkService {
log.debug("enter", metadata: ["func": "\(#function)"])
defer { log.debug("exit", metadata: ["func": "\(#function)"]) }

try refreshNetworkStateIfNeeded()
guard let status = await network.status else {
throw ContainerizationError(.invalidState, message: "network \(network.id) must be running")
}
try resetAllocatorIfNeeded(status: status)

let macAddress = macAddress ?? MACAddress((UInt64.random(in: 0...UInt64.max) & 0x0cff_ffff_ffff) | 0xf200_0000_0000)
let index = try await allocator.allocate(hostname: hostname)
Expand Down Expand Up @@ -122,9 +127,11 @@ public actor DefaultNetworkService: NetworkService {
log.debug("enter", metadata: ["func": "\(#function)"])
defer { log.debug("exit", metadata: ["func": "\(#function)"]) }

try refreshNetworkStateIfNeeded()
guard let status = await network.status else {
throw ContainerizationError(.invalidState, message: "network \(network.id) must be running")
}
try resetAllocatorIfNeeded(status: status)

// Invariant: hostname -> index if and only if index -> MAC address
let index = try await allocator.lookup(hostname: hostname)
Expand Down Expand Up @@ -157,4 +164,24 @@ public actor DefaultNetworkService: NetworkService {

return attachment
}

private func refreshNetworkStateIfNeeded() throws {
try network.withAdditionalData { _ in }
}

private func resetAllocatorIfNeeded(status: NetworkStatus) throws {
let subnet = status.ipv4Subnet
guard allocatorSubnet.description != subnet.description else {
return
}

guard allocationsBySession.isEmpty else {
throw ContainerizationError(.invalidState, message: "network \(network.id) changed subnet while allocations are active")
}

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