Skip to content

Commit 5064b0f

Browse files
authored
Adds network IPv6 configuration. (apple#975)
- Part of work for apple#460. - Enable set/get of IPv6 network prefix in ReservedVmnetNetwork. - Show IPv6 prefix in `network list` full output. - Option for setting IPv6 prefix when creating a network. - System property for default IPv6 prefix. ## Type of Change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [x] Documentation update ## Motivation and Context See apple#460. ## Testing - [x] Tested locally - [ ] Added/updated tests - [x] Added/updated docs
1 parent 9c239aa commit 5064b0f

File tree

14 files changed

+173
-60
lines changed

14 files changed

+173
-60
lines changed

‎Package.resolved‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Package.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import PackageDescription
2323
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
2424
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
2525
let builderShimVersion = "0.7.0"
26-
let scVersion = "0.17.0"
26+
let scVersion = "0.18.0"
2727

2828
let package = Package(
2929
name: "container",

‎Sources/ContainerCommands/Network/NetworkCreate.swift‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ extension Application {
3232
var labels: [String] = []
3333

3434
@Option(name: .customLong("subnet"), help: "Set subnet for a network")
35-
var subnet: String? = nil
35+
var ipv4Subnet: String? = nil
36+
37+
@Option(name: .customLong("subnet-v6"), help: "Set the IPv6 prefix for a network")
38+
var ipv6Subnet: String? = nil
3639

3740
@OptionGroup
3841
var global: Flags.Global
@@ -44,8 +47,9 @@ extension Application {
4447

4548
public func run() async throws {
4649
let parsedLabels = Utility.parseKeyValuePairs(labels)
47-
let ipv4Subnet = try subnet.map { try CIDRv4($0) }
48-
let config = try NetworkConfiguration(id: self.name, mode: .nat, ipv4Subnet: ipv4Subnet, labels: parsedLabels)
50+
let ipv4Subnet = try ipv4Subnet.map { try CIDRv4($0) }
51+
let ipv6Subnet = try ipv6Subnet.map { try CIDRv6($0) }
52+
let config = try NetworkConfiguration(id: self.name, mode: .nat, ipv4Subnet: ipv4Subnet, ipv6Subnet: ipv6Subnet, labels: parsedLabels)
4953
let state = try await ClientNetwork.create(configuration: config)
5054
print(state.id)
5155
}

‎Sources/ContainerCommands/System/Property/PropertySet.swift‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ extension Application {
7474
throw ContainerizationError(.invalidArgument, message: "invalid CIDRv4 address: \(value)")
7575
}
7676
DefaultsStore.set(value: value, key: key)
77+
case .defaultIPv6Subnet:
78+
guard (try? CIDRv6(value)) != nil else {
79+
throw ContainerizationError(.invalidArgument, message: "invalid CIDRv6 address: \(value)")
80+
}
81+
DefaultsStore.set(value: value, key: key)
7782
}
7883
}
7984
}

‎Sources/ContainerPersistence/DefaultsStore.swift‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public enum DefaultsStore {
3030
case defaultKernelBinaryPath = "kernel.binaryPath"
3131
case defaultKernelURL = "kernel.url"
3232
case defaultSubnet = "network.subnet"
33+
case defaultIPv6Subnet = "network.subnetv6"
3334
case defaultRegistryDomain = "registry.domain"
3435
}
3536

@@ -73,6 +74,7 @@ public enum DefaultsStore {
7374
(.defaultKernelBinaryPath, { Self.get(key: $0) }),
7475
(.defaultKernelURL, { Self.get(key: $0) }),
7576
(.defaultSubnet, { Self.getOptional(key: $0) }),
77+
(.defaultIPv6Subnet, { Self.getOptional(key: $0) }),
7678
(.defaultDNSDomain, { Self.getOptional(key: $0) }),
7779
(.defaultRegistryDomain, { Self.get(key: $0) }),
7880
]
@@ -131,7 +133,9 @@ extension DefaultsStore.Keys {
131133
case .defaultKernelURL:
132134
return "The URL for the kernel file to install, or the URL for an archive containing the kernel file."
133135
case .defaultSubnet:
134-
return "Default subnet for IP allocation (used on macOS 15 only)."
136+
return "Default subnet for IPv4 allocation."
137+
case .defaultIPv6Subnet:
138+
return "Default IPv6 network prefix."
135139
case .defaultRegistryDomain:
136140
return "The default registry to use for image references that do not specify a registry."
137141
}
@@ -153,6 +157,8 @@ extension DefaultsStore.Keys {
153157
return String.self
154158
case .defaultSubnet:
155159
return String.self
160+
case .defaultIPv6Subnet:
161+
return String.self
156162
case .defaultRegistryDomain:
157163
return String.self
158164
}
@@ -180,6 +186,8 @@ extension DefaultsStore.Keys {
180186
return "https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz"
181187
case .defaultSubnet:
182188
return "192.168.64.1/24"
189+
case .defaultIPv6Subnet:
190+
return "fd00::/64"
183191
case .defaultRegistryDomain:
184192
return "docker.io"
185193
}

‎Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ extension NetworkVmnetHelper {
3737
@Option(name: .shortAndLong, help: "Network identifier")
3838
var id: String
3939

40-
@Option(name: .shortAndLong, help: "CIDR address for the subnet")
41-
var subnet: String?
40+
@Option(name: .customLong("subnet"), help: "CIDR address for the IPv4 subnet")
41+
var ipv4Subnet: String?
42+
43+
@Option(name: .customLong("subnet-v6"), help: "CIDR address for the IPv6 prefix")
44+
var ipv6Subnet: String?
4245

4346
func run() async throws {
4447
let commandName = NetworkVmnetHelper._commandName
@@ -50,8 +53,14 @@ extension NetworkVmnetHelper {
5053

5154
do {
5255
log.info("configuring XPC server")
53-
let ipv4Subnet = try self.subnet.map { try CIDRv4($0) }
54-
let configuration = try NetworkConfiguration(id: id, mode: .nat, ipv4Subnet: ipv4Subnet)
56+
let ipv4Subnet = try self.ipv4Subnet.map { try CIDRv4($0) }
57+
let ipv6Subnet = try self.ipv6Subnet.map { try CIDRv6($0) }
58+
let configuration = try NetworkConfiguration(
59+
id: id,
60+
mode: .nat,
61+
ipv4Subnet: ipv4Subnet,
62+
ipv6Subnet: ipv6Subnet,
63+
)
5564
let network = try Self.createNetwork(configuration: configuration, log: log)
5665
try await network.start()
5766
let server = try await NetworkService(network: network, log: log)

‎Sources/Services/ContainerAPIService/Networks/NetworksService.swift‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public actor NetworksService {
283283
serviceIdentifier,
284284
]
285285

286-
if let ipv4Subnet = (configuration.ipv4Subnet.map { $0 }) {
286+
if let ipv4Subnet = configuration.ipv4Subnet {
287287
var existingCidrs: [CIDRv4] = []
288288
for networkState in networkStates.values {
289289
if case .running(_, let status) = networkState {
@@ -303,6 +303,26 @@ public actor NetworksService {
303303
args += ["--subnet", ipv4Subnet.description]
304304
}
305305

306+
if let ipv6Subnet = configuration.ipv6Subnet {
307+
var existingCidrs: [CIDRv6] = []
308+
for networkState in networkStates.values {
309+
if case .running(_, let status) = networkState, let otherIPv6Subnet = status.ipv6Subnet {
310+
existingCidrs.append(otherIPv6Subnet)
311+
}
312+
}
313+
let overlap = existingCidrs.first {
314+
$0.contains(ipv6Subnet.lower)
315+
|| $0.contains(ipv6Subnet.upper)
316+
|| ipv6Subnet.contains($0.lower)
317+
|| ipv6Subnet.contains($0.upper)
318+
}
319+
if let overlap {
320+
throw ContainerizationError(.exists, message: "IPv6 subnet \(ipv6Subnet) overlaps an existing network with subnet \(overlap)")
321+
}
322+
323+
args += ["--subnet-v6", ipv6Subnet.description]
324+
}
325+
306326
try await pluginLoader.registerWithLaunchd(
307327
plugin: networkPlugin,
308328
pluginStateRoot: store.entityUrl(configuration.id),

‎Sources/Services/ContainerNetworkService/AllocationOnlyVmnetNetwork.swift‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ public actor AllocationOnlyVmnetNetwork: Network {
6767
let subnet = DefaultsStore.get(key: .defaultSubnet)
6868
let subnetCIDR = try CIDRv4(subnet)
6969
let gateway = IPv4Address(subnetCIDR.lower.value + 1)
70-
self._state = .running(configuration, NetworkStatus(ipv4Subnet: subnetCIDR, ipv4Gateway: gateway))
70+
let status = NetworkStatus(
71+
ipv4Subnet: subnetCIDR,
72+
ipv4Gateway: gateway,
73+
ipv6Subnet: nil,
74+
)
75+
self._state = .running(configuration, status)
7176
log.info(
7277
"started allocation-only network",
7378
metadata: [

‎Sources/Services/ContainerNetworkService/NetworkConfiguration.swift‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable {
3232
/// The preferred CIDR address for the IPv4 subnet, if specified
3333
public let ipv4Subnet: CIDRv4?
3434

35+
/// The preferred CIDR address for the IPv6 subnet, if specified
36+
public let ipv6Subnet: CIDRv6?
37+
3538
/// Key-value labels for the network.
3639
public var labels: [String: String] = [:]
3740

@@ -40,12 +43,14 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable {
4043
id: String,
4144
mode: NetworkMode,
4245
ipv4Subnet: CIDRv4? = nil,
46+
ipv6Subnet: CIDRv6? = nil,
4347
labels: [String: String] = [:]
4448
) throws {
4549
self.id = id
4650
self.creationDate = Date()
4751
self.mode = mode
4852
self.ipv4Subnet = ipv4Subnet
53+
self.ipv6Subnet = ipv6Subnet
4954
self.labels = labels
5055
try validate()
5156
}
@@ -55,6 +60,7 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable {
5560
case creationDate
5661
case mode
5762
case ipv4Subnet
63+
case ipv6Subnet
5864
case labels
5965
// TODO: retain for deserialization compatability for now, remove later
6066
case subnet
@@ -72,6 +78,8 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable {
7278
try container.decodeIfPresent(String.self, forKey: .ipv4Subnet)
7379
?? container.decodeIfPresent(String.self, forKey: .subnet)
7480
ipv4Subnet = try subnetText.map { try CIDRv4($0) }
81+
ipv6Subnet = try container.decodeIfPresent(String.self, forKey: .ipv6Subnet)
82+
.map { try CIDRv6($0) }
7583
labels = try container.decodeIfPresent([String: String].self, forKey: .labels) ?? [:]
7684
try validate()
7785
}
@@ -83,7 +91,8 @@ public struct NetworkConfiguration: Codable, Sendable, Identifiable {
8391
try container.encode(id, forKey: .id)
8492
try container.encode(creationDate, forKey: .creationDate)
8593
try container.encode(mode, forKey: .mode)
86-
try container.encodeIfPresent(ipv4Subnet?.description, forKey: .ipv4Subnet)
94+
try container.encodeIfPresent(ipv4Subnet, forKey: .ipv4Subnet)
95+
try container.encodeIfPresent(ipv6Subnet, forKey: .ipv6Subnet)
8796
try container.encode(labels, forKey: .labels)
8897
}
8998

‎Sources/Services/ContainerNetworkService/NetworkState.swift‎

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,23 @@ public struct NetworkStatus: Codable, Sendable {
2121
/// The address allocated for the network if no subnet was specified at
2222
/// creation time; otherwise, the subnet from the configuration.
2323
public let ipv4Subnet: CIDRv4
24+
2425
/// The gateway IPv4 address.
2526
public let ipv4Gateway: IPv4Address
2627

28+
/// The address allocated for the IPv6 network if no subnet was specified at
29+
/// creation time; otherwise, the IPv6 subnet from the configuration.
30+
public let ipv6Subnet: CIDRv6?
31+
2732
public init(
2833
ipv4Subnet: CIDRv4,
29-
ipv4Gateway: IPv4Address
34+
ipv4Gateway: IPv4Address,
35+
ipv6Subnet: CIDRv6?,
3036
) {
3137
self.ipv4Subnet = ipv4Subnet
3238
self.ipv4Gateway = ipv4Gateway
39+
self.ipv6Subnet = ipv6Subnet
3340
}
34-
35-
enum CodingKeys: String, CodingKey {
36-
case ipv4Subnet
37-
case ipv4Gateway
38-
}
39-
40-
/// Create a network status from the supplied Decoder.
41-
public init(from decoder: Decoder) throws {
42-
let container = try decoder.container(keyedBy: CodingKeys.self)
43-
44-
let addressText = try container.decode(String.self, forKey: .ipv4Subnet)
45-
ipv4Subnet = try CIDRv4(addressText)
46-
let gatewayText = try container.decode(String.self, forKey: .ipv4Gateway)
47-
ipv4Gateway = try IPv4Address(gatewayText)
48-
}
49-
50-
/// Encode the network status to the supplied Encoder.
51-
public func encode(to encoder: Encoder) throws {
52-
var container = encoder.container(keyedBy: CodingKeys.self)
53-
54-
try container.encode(ipv4Subnet.description, forKey: .ipv4Subnet)
55-
try container.encode(ipv4Gateway.description, forKey: .ipv4Gateway)
56-
}
57-
5841
}
5942

6043
/// The configuration and runtime attributes for a network.

0 commit comments

Comments
 (0)