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
5 changes: 4 additions & 1 deletion Configuration/UTMAppleConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ enum UTMAppleConfigurationError: Error {
case hardwareModelInvalid
case rosettaNotSupported
case featureNotSupported
case networkConfigurationFailed
}

extension UTMAppleConfigurationError: LocalizedError {
Expand All @@ -118,6 +119,8 @@ extension UTMAppleConfigurationError: LocalizedError {
return NSLocalizedString("Rosetta is not supported on the current host machine.", comment: "UTMAppleConfiguration")
case .featureNotSupported:
return NSLocalizedString("The host operating system needs to be updated to support one or more features requested by the guest.", comment: "UTMAppleConfiguration")
case .networkConfigurationFailed:
return NSLocalizedString("The requested network mode could not be configured on this host.", comment: "UTMAppleConfiguration")
}
}
}
Expand Down Expand Up @@ -279,7 +282,7 @@ extension UTMAppleConfiguration {
}
}
}
vzconfig.networkDevices.append(contentsOf: networks.compactMap({ $0.vzNetworking() }))
vzconfig.networkDevices.append(contentsOf: try networks.compactMap({ try $0.vzNetworking() }))
vzconfig.serialPorts.append(contentsOf: serials.compactMap({ $0.vzSerial() }))
// add remaining devices
try virtualization.fillVZConfiguration(vzconfig, isMacOSGuest: system.boot.operatingSystem == .macOS)
Expand Down
71 changes: 70 additions & 1 deletion Configuration/UTMAppleConfigurationNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@

import Foundation
import Virtualization
#if os(macOS) && compiler(>=6.3)
import ObjectiveC
import vmnet
#endif

@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 11, *)
struct UTMAppleConfigurationNetwork: Codable, Identifiable {
enum NetworkMode: String, CaseIterable, QEMUConstant {
case shared = "Shared"
case host = "Host"
case bridged = "Bridged"

var prettyValue: String {
switch self {
case .shared: return NSLocalizedString("Shared Network", comment: "UTMAppleConfigurationNetwork")
case .host: return NSLocalizedString("Host Only", comment: "UTMAppleConfigurationNetwork")
case .bridged: return NSLocalizedString("Bridged (Advanced)", comment: "UTMAppleConfigurationNetwork")
}
}
Expand Down Expand Up @@ -78,11 +84,17 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
} else if let _ = virtioConfig.attachment as? VZNATNetworkDeviceAttachment {
mode = .shared
} else {
#if os(macOS) && compiler(>=6.3)
if #available(macOS 26, *), let _ = virtioConfig.attachment as? VZVmnetNetworkDeviceAttachment {
mode = .host
return
}
#endif
return nil
}
}

func vzNetworking() -> VZNetworkDeviceConfiguration? {
func vzNetworking() throws -> VZNetworkDeviceConfiguration? {
let config = VZVirtioNetworkDeviceConfiguration()
guard let macAddress = VZMACAddress(string: macAddress) else {
return nil
Expand All @@ -92,6 +104,16 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
case .shared:
let attachment = VZNATNetworkDeviceAttachment()
config.attachment = attachment
case .host:
#if os(macOS) && compiler(>=6.3)
if #available(macOS 26, *) {
config.attachment = try makeVmnetHostNetworkAttachment()
} else {
throw UTMAppleConfigurationError.featureNotSupported
}
#else
throw UTMAppleConfigurationError.featureNotSupported
#endif
case .bridged:
var found: VZBridgedNetworkInterface?
if let bridgeInterface = bridgeInterface {
Expand All @@ -114,6 +136,53 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
}
}

#if os(macOS) && compiler(>=6.3)
@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 26, *)
private final class UTMAppleVmnetNetworkReference {
private let configuration: CFTypeRef
private let network: CFTypeRef

init(configuration: CFTypeRef, network: vmnet_network_ref) {
self.configuration = configuration
self.network = Unmanaged<CFTypeRef>.fromOpaque(UnsafeRawPointer(network)).takeRetainedValue()
}
}

@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 26, *)
private var vmnetNetworkReferenceAssociationKey: UInt8 = 0

@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
@available(macOS 26, *)
private extension UTMAppleConfigurationNetwork {
func makeVmnetHostNetworkAttachment() throws -> VZNetworkDeviceAttachment {
var status: vmnet_return_t = .VMNET_SUCCESS
guard let configuration = vmnet_network_configuration_create(.VMNET_HOST_MODE, &status) else {
throw UTMAppleConfigurationError.networkConfigurationFailed
}
let configurationReference = Unmanaged<CFTypeRef>.fromOpaque(UnsafeRawPointer(configuration)).takeRetainedValue()
guard status == .VMNET_SUCCESS else {
throw UTMAppleConfigurationError.networkConfigurationFailed
}

status = .VMNET_SUCCESS
guard let network = vmnet_network_create(configuration, &status) else {
throw UTMAppleConfigurationError.networkConfigurationFailed
}
let reference = UTMAppleVmnetNetworkReference(configuration: configurationReference, network: network)
guard status == .VMNET_SUCCESS else {
_ = reference
throw UTMAppleConfigurationError.networkConfigurationFailed
}

let attachment = VZVmnetNetworkDeviceAttachment(network: network)
objc_setAssociatedObject(attachment, &vmnetNetworkReferenceAssociationKey, reference, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return attachment
}
}
#endif

// MARK: - Conversion of old config format

@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
Expand Down
31 changes: 30 additions & 1 deletion Platform/macOS/VMConfigAppleNetworkingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,27 @@ struct VMConfigAppleNetworkingView: View {
@EnvironmentObject private var data: UTMData
@State private var newMacAddress: String?

private var isHostOnlySupported: Bool {
#if compiler(>=6.3)
if #available(macOS 26, *) {
return true
} else {
return false
}
#else
return false
#endif
}

var body: some View {
Form {
VMConfigConstantPicker("Network Mode", selection: $config.mode)
Picker("Network Mode", selection: $config.mode) {
ForEach(UTMAppleConfigurationNetwork.NetworkMode.allCases, id: \.rawValue) { mode in
Text(mode.prettyValue)
.tag(mode)
.disabled(mode == .host && !isHostOnlySupported)
}
}
HStack {
TextField("MAC Address", text: $newMacAddress.bound, onCommit: {
commitMacAddress()
Expand All @@ -38,6 +56,17 @@ struct VMConfigAppleNetworkingView: View {
commitMacAddress()
}
}
if config.mode == .host {
Section(header: Text("Host Only Settings")) {
if isHostOnlySupported {
Text("The guest can communicate with this Mac, but not the internet.")
.foregroundColor(.secondary)
} else {
Text("Host Only requires macOS 26 or later.")
.foregroundColor(.secondary)
}
}
}
if config.mode == .bridged {
Section(header: Text("Bridged Settings")) {
Picker("Interface", selection: $config.bridgeInterface) {
Expand Down
1 change: 1 addition & 0 deletions Scripting/UTM.sdef
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@

<enumeration name="apple network mode" code="ApNm" description="Mode for networking device.">
<enumerator name="shared" code="ShRd" description="NAT based sharing with the host."/>
<enumerator name="host" code="HoSt" description="Host-only network with no internet routing."/>
<enumerator name="bridged" code="BrGd" description="Bridged to a host interface."/>
</enumeration>

Expand Down
2 changes: 1 addition & 1 deletion Scripting/UTMScripting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ import ScriptingBridge
// MARK: UTMScriptingAppleNetworkMode
@objc public enum UTMScriptingAppleNetworkMode : AEKeyword {
case shared = 0x53685264 /* 'ShRd' */
case host = 0x486f5374 /* 'HoSt' */
case bridged = 0x42724764 /* 'BrGd' */
}

Expand Down Expand Up @@ -288,4 +289,3 @@ extension SBObject: UTMScriptingGuestProcess {}
@objc optional func disconnect() // Disconnect a USB device from the guest and re-assign it to the host.
}
extension SBObject: UTMScriptingUsbDevice {}

4 changes: 3 additions & 1 deletion Scripting/UTMScriptingConfigImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ extension UTMScriptingConfigImpl {
private func appleNetworkMode(from mode: UTMAppleConfigurationNetwork.NetworkMode) -> UTMScriptingAppleNetworkMode {
switch mode {
case .shared: return .shared
case .host: return .host
case .bridged: return .bridged
}
}
Expand Down Expand Up @@ -697,11 +698,12 @@ extension UTMScriptingConfigImpl {
}

private func parseAppleNetworkMode(_ value: AEKeyword?) -> UTMAppleConfigurationNetwork.NetworkMode? {
guard let value = value, let parsed = UTMScriptingQemuNetworkMode(rawValue: value) else {
guard let value = value, let parsed = UTMScriptingAppleNetworkMode(rawValue: value) else {
return Optional.none
}
switch parsed {
case .shared: return .shared
case .host: return .host
case .bridged: return .bridged
default: return Optional.none
}
Expand Down