Skip to content

Commit 5a694b6

Browse files
committed
Containerization: Rename nat interfaces
These were fairly generically named, and didn't give any hints to their ties to the various OS frameworks. Signed-off-by: Danny Canter <danny_canter@apple.com>
1 parent 9959810 commit 5a694b6

5 files changed

Lines changed: 183 additions & 28 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
// All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//===----------------------------------------------------------------------===//
17+
18+
#if os(macOS)
19+
20+
import vmnet
21+
import Virtualization
22+
import ContainerizationError
23+
import Foundation
24+
import Synchronization
25+
26+
/// A type that uses vmnet (https://developer.apple.com/documentation/vmnet)
27+
/// to provide a NAT interface for a given container/virtual machine.
28+
/// A `vmnet_network_ref` is provided to the type that should be pre-programmed
29+
/// by the client before passing to this class.
30+
@available(macOS 16, *)
31+
public final class VMNetNATNetworkInterface: Interface, Sendable {
32+
public var address: String {
33+
get { state.withLock { $0.address } }
34+
set { state.withLock { $0.address = newValue } }
35+
}
36+
37+
public var gateway: String {
38+
get { state.withLock { $0.gateway } }
39+
set { state.withLock { $0.gateway = newValue } }
40+
}
41+
42+
public var macAddress: String? {
43+
get { state.withLock { $0.macAddress } }
44+
set { state.withLock { $0.macAddress = newValue } }
45+
}
46+
47+
struct State {
48+
var address: String
49+
var gateway: String
50+
var macAddress: String?
51+
#if !CURRENT_SDK
52+
var reference: vmnet_network_ref
53+
#endif
54+
}
55+
56+
#if !CURRENT_SDK
57+
public var reference: vmnet_network_ref {
58+
state.withLock { $0.reference }
59+
}
60+
#endif
61+
62+
private let state: Mutex<State>
63+
#if !CURRENT_SDK
64+
public init(
65+
address: String,
66+
gateway: String,
67+
reference: sending vmnet_network_ref,
68+
macAddress: String? = nil
69+
) {
70+
self.state = .init(
71+
.init(
72+
address: address,
73+
gateway: gateway,
74+
macAddress: macAddress,
75+
reference: reference
76+
)
77+
)
78+
}
79+
#else
80+
public init(
81+
address: String,
82+
gateway: String,
83+
macAddress: String? = nil
84+
) {
85+
self.state = .init(
86+
.init(
87+
address: address,
88+
gateway: gateway,
89+
macAddress: macAddress
90+
)
91+
)
92+
}
93+
#endif
94+
}
95+
96+
@available(macOS 16, *)
97+
extension VMNetNATNetworkInterface: VZInterface {
98+
public func device() throws -> VZVirtioNetworkDeviceConfiguration {
99+
let config = VZVirtioNetworkDeviceConfiguration()
100+
if let macAddress = self.macAddress {
101+
guard let mac = VZMACAddress(string: macAddress) else {
102+
throw ContainerizationError(.invalidArgument, message: "invalid mac address \(macAddress)")
103+
}
104+
config.macAddress = mac
105+
}
106+
107+
#if !CURRENT_SDK
108+
config.attachment = VZVmnetNetworkDeviceAttachment(network: self.reference)
109+
#else
110+
config.attachment = VZNATNetworkDeviceAttachment()
111+
#endif
112+
return config
113+
}
114+
}
115+
116+
#endif

Sources/Containerization/NATInterface.swift renamed to Sources/Containerization/VZInterface.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
// limitations under the License.
1616
//===----------------------------------------------------------------------===//
1717

18-
public struct NATInterface: Interface {
19-
public var address: String
20-
public var gateway: String
21-
public var macAddress: String?
18+
#if os(macOS)
19+
import Virtualization
2220

23-
public init(address: String, gateway: String, macAddress: String? = nil) {
24-
self.address = address
25-
self.gateway = gateway
26-
self.macAddress = macAddress
27-
}
21+
/// A protocol to implement to convert your interface definition to
22+
/// Virtualization.framework's VZVirtioNetworkDeviceConfiguration.
23+
/// This is the definition Virtualization.framework uses to setup
24+
/// interfaces for virtual machines.
25+
public protocol VZInterface {
26+
/// Return a valid `VZVirtioNetworkDeviceConfiguration`.
27+
func device() throws -> VZVirtioNetworkDeviceConfiguration
2828
}
29+
30+
#endif
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
// All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
//===----------------------------------------------------------------------===//
17+
18+
#if os(macOS)
19+
20+
import ContainerizationError
21+
import Virtualization
22+
23+
/// Legacy NAT network interface backed by Virtualization.framework.
24+
public struct VZNATInterface: Interface {
25+
public var address: String
26+
public var gateway: String
27+
public var macAddress: String?
28+
29+
public init(address: String, gateway: String, macAddress: String? = nil) {
30+
self.address = address
31+
self.gateway = gateway
32+
self.macAddress = macAddress
33+
}
34+
}
35+
36+
extension VZNATInterface: VZInterface {
37+
/// Turns the provided data on the interface into a valid
38+
/// Virtualization.framework `VZVirtioNetworkDeviceConfiguration`.
39+
public func device() throws -> VZVirtioNetworkDeviceConfiguration {
40+
let config = VZVirtioNetworkDeviceConfiguration()
41+
if let macAddress = self.macAddress {
42+
guard let mac = VZMACAddress(string: macAddress) else {
43+
throw ContainerizationError(
44+
.invalidArgument,
45+
message: "invalid mac address \(macAddress)"
46+
)
47+
}
48+
config.macAddress = mac
49+
}
50+
config.attachment = VZNATNetworkDeviceAttachment()
51+
return config
52+
}
53+
}
54+
55+
#endif

Sources/Containerization/VZVirtualMachineInstance.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -364,22 +364,4 @@ extension Kernel {
364364
}
365365
}
366366

367-
public protocol VZInterface {
368-
func device() throws -> VZVirtioNetworkDeviceConfiguration
369-
}
370-
371-
extension NATInterface: VZInterface {
372-
public func device() throws -> VZVirtioNetworkDeviceConfiguration {
373-
let config = VZVirtioNetworkDeviceConfiguration()
374-
if let macAddress = self.macAddress {
375-
guard let mac = VZMACAddress(string: macAddress) else {
376-
throw ContainerizationError(.invalidArgument, message: "invalid mac address \(macAddress)")
377-
}
378-
config.macAddress = mac
379-
}
380-
config.attachment = VZNATNetworkDeviceAttachment()
381-
return config
382-
}
383-
}
384-
385367
#endif

Sources/cctl/RunCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ extension Application {
128128
guard let gateway else {
129129
throw ContainerizationError(.invalidArgument, message: "gateway must be specified")
130130
}
131-
container.interfaces.append(NATInterface(address: ip, gateway: gateway))
131+
container.interfaces.append(VZNATInterface(address: ip, gateway: gateway))
132132
container.dns = .init(nameservers: [gateway])
133133
if nameservers.count > 0 {
134134
container.dns = .init(nameservers: nameservers)

0 commit comments

Comments
 (0)