From 5a694b677dd0bc08b63ff10f18942087cf4f3755 Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Fri, 6 Jun 2025 15:58:02 -0700 Subject: [PATCH] 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 --- .../VMNetNATNetworkInterface.swift | 116 ++++++++++++++++++ .../{NATInterface.swift => VZInterface.swift} | 20 +-- Sources/Containerization/VZNATInterface.swift | 55 +++++++++ .../VZVirtualMachineInstance.swift | 18 --- Sources/cctl/RunCommand.swift | 2 +- 5 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 Sources/Containerization/VMNetNATNetworkInterface.swift rename Sources/Containerization/{NATInterface.swift => VZInterface.swift} (65%) create mode 100644 Sources/Containerization/VZNATInterface.swift diff --git a/Sources/Containerization/VMNetNATNetworkInterface.swift b/Sources/Containerization/VMNetNATNetworkInterface.swift new file mode 100644 index 0000000..9c7cbb8 --- /dev/null +++ b/Sources/Containerization/VMNetNATNetworkInterface.swift @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +#if os(macOS) + +import vmnet +import Virtualization +import ContainerizationError +import Foundation +import Synchronization + +/// A type that uses vmnet (https://developer.apple.com/documentation/vmnet) +/// to provide a NAT interface for a given container/virtual machine. +/// A `vmnet_network_ref` is provided to the type that should be pre-programmed +/// by the client before passing to this class. +@available(macOS 16, *) +public final class VMNetNATNetworkInterface: Interface, Sendable { + public var address: String { + get { state.withLock { $0.address } } + set { state.withLock { $0.address = newValue } } + } + + public var gateway: String { + get { state.withLock { $0.gateway } } + set { state.withLock { $0.gateway = newValue } } + } + + public var macAddress: String? { + get { state.withLock { $0.macAddress } } + set { state.withLock { $0.macAddress = newValue } } + } + + struct State { + var address: String + var gateway: String + var macAddress: String? + #if !CURRENT_SDK + var reference: vmnet_network_ref + #endif + } + + #if !CURRENT_SDK + public var reference: vmnet_network_ref { + state.withLock { $0.reference } + } + #endif + + private let state: Mutex + #if !CURRENT_SDK + public init( + address: String, + gateway: String, + reference: sending vmnet_network_ref, + macAddress: String? = nil + ) { + self.state = .init( + .init( + address: address, + gateway: gateway, + macAddress: macAddress, + reference: reference + ) + ) + } + #else + public init( + address: String, + gateway: String, + macAddress: String? = nil + ) { + self.state = .init( + .init( + address: address, + gateway: gateway, + macAddress: macAddress + ) + ) + } + #endif +} + +@available(macOS 16, *) +extension VMNetNATNetworkInterface: VZInterface { + public func device() throws -> VZVirtioNetworkDeviceConfiguration { + let config = VZVirtioNetworkDeviceConfiguration() + if let macAddress = self.macAddress { + guard let mac = VZMACAddress(string: macAddress) else { + throw ContainerizationError(.invalidArgument, message: "invalid mac address \(macAddress)") + } + config.macAddress = mac + } + + #if !CURRENT_SDK + config.attachment = VZVmnetNetworkDeviceAttachment(network: self.reference) + #else + config.attachment = VZNATNetworkDeviceAttachment() + #endif + return config + } +} + +#endif diff --git a/Sources/Containerization/NATInterface.swift b/Sources/Containerization/VZInterface.swift similarity index 65% rename from Sources/Containerization/NATInterface.swift rename to Sources/Containerization/VZInterface.swift index 7b44e88..c0fb707 100644 --- a/Sources/Containerization/NATInterface.swift +++ b/Sources/Containerization/VZInterface.swift @@ -15,14 +15,16 @@ // limitations under the License. //===----------------------------------------------------------------------===// -public struct NATInterface: Interface { - public var address: String - public var gateway: String - public var macAddress: String? +#if os(macOS) +import Virtualization - public init(address: String, gateway: String, macAddress: String? = nil) { - self.address = address - self.gateway = gateway - self.macAddress = macAddress - } +/// A protocol to implement to convert your interface definition to +/// Virtualization.framework's VZVirtioNetworkDeviceConfiguration. +/// This is the definition Virtualization.framework uses to setup +/// interfaces for virtual machines. +public protocol VZInterface { + /// Return a valid `VZVirtioNetworkDeviceConfiguration`. + func device() throws -> VZVirtioNetworkDeviceConfiguration } + +#endif diff --git a/Sources/Containerization/VZNATInterface.swift b/Sources/Containerization/VZNATInterface.swift new file mode 100644 index 0000000..886ab15 --- /dev/null +++ b/Sources/Containerization/VZNATInterface.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +#if os(macOS) + +import ContainerizationError +import Virtualization + +/// Legacy NAT network interface backed by Virtualization.framework. +public struct VZNATInterface: Interface { + public var address: String + public var gateway: String + public var macAddress: String? + + public init(address: String, gateway: String, macAddress: String? = nil) { + self.address = address + self.gateway = gateway + self.macAddress = macAddress + } +} + +extension VZNATInterface: VZInterface { + /// Turns the provided data on the interface into a valid + /// Virtualization.framework `VZVirtioNetworkDeviceConfiguration`. + public func device() throws -> VZVirtioNetworkDeviceConfiguration { + let config = VZVirtioNetworkDeviceConfiguration() + if let macAddress = self.macAddress { + guard let mac = VZMACAddress(string: macAddress) else { + throw ContainerizationError( + .invalidArgument, + message: "invalid mac address \(macAddress)" + ) + } + config.macAddress = mac + } + config.attachment = VZNATNetworkDeviceAttachment() + return config + } +} + +#endif diff --git a/Sources/Containerization/VZVirtualMachineInstance.swift b/Sources/Containerization/VZVirtualMachineInstance.swift index a5107ea..e67b3e1 100644 --- a/Sources/Containerization/VZVirtualMachineInstance.swift +++ b/Sources/Containerization/VZVirtualMachineInstance.swift @@ -364,22 +364,4 @@ extension Kernel { } } -public protocol VZInterface { - func device() throws -> VZVirtioNetworkDeviceConfiguration -} - -extension NATInterface: VZInterface { - public func device() throws -> VZVirtioNetworkDeviceConfiguration { - let config = VZVirtioNetworkDeviceConfiguration() - if let macAddress = self.macAddress { - guard let mac = VZMACAddress(string: macAddress) else { - throw ContainerizationError(.invalidArgument, message: "invalid mac address \(macAddress)") - } - config.macAddress = mac - } - config.attachment = VZNATNetworkDeviceAttachment() - return config - } -} - #endif diff --git a/Sources/cctl/RunCommand.swift b/Sources/cctl/RunCommand.swift index 19a0f33..fa74e21 100644 --- a/Sources/cctl/RunCommand.swift +++ b/Sources/cctl/RunCommand.swift @@ -128,7 +128,7 @@ extension Application { guard let gateway else { throw ContainerizationError(.invalidArgument, message: "gateway must be specified") } - container.interfaces.append(NATInterface(address: ip, gateway: gateway)) + container.interfaces.append(VZNATInterface(address: ip, gateway: gateway)) container.dns = .init(nameservers: [gateway]) if nameservers.count > 0 { container.dns = .init(nameservers: nameservers)