Skip to content
Draft
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
48 changes: 46 additions & 2 deletions Configuration/UTMAppleConfigurationNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
enum NetworkMode: String, CaseIterable, QEMUConstant {
case shared = "Shared"
case bridged = "Bridged"
case host = "Host"

var prettyValue: String {
switch self {
case .shared: return NSLocalizedString("Shared Network", comment: "UTMAppleConfigurationNetwork")
case .bridged: return NSLocalizedString("Bridged (Advanced)", comment: "UTMAppleConfigurationNetwork")
case .host: return NSLocalizedString("Host (Advanced)", comment: "UTMAppleConfigurationNetwork")
}
}
}
Expand All @@ -40,12 +42,16 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
/// In bridged mode this is the physical interface to bridge.
var bridgeInterface: String?

/// Network UUID to attach to in host mode
var hostNetUuid: String?

let id = UUID()

enum CodingKeys: String, CodingKey {
case mode = "Mode"
case macAddress = "MacAddress"
case bridgeInterface = "BridgeInterface"
case hostNetUuid = "HostNetUuid"
}

init() {
Expand All @@ -56,6 +62,7 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
mode = try values.decode(NetworkMode.self, forKey: .mode)
macAddress = try values.decode(String.self, forKey: .macAddress)
bridgeInterface = try values.decodeIfPresent(String.self, forKey: .bridgeInterface)
hostNetUuid = try values.decodeIfPresent(UUID.self, forKey: .hostNetUuid)?.uuidString
}

func encode(to encoder: Encoder) throws {
Expand All @@ -65,6 +72,9 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
if mode == .bridged {
try container.encodeIfPresent(bridgeInterface, forKey: .bridgeInterface)
}
if mode == .host {
try container.encodeIfPresent(hostNetUuid, forKey: .hostNetUuid)
}
}

init?(from config: VZNetworkDeviceConfiguration) {
Expand All @@ -77,9 +87,26 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
bridgeInterface = attachment.interface.identifier
} else if let _ = virtioConfig.attachment as? VZNATNetworkDeviceAttachment {
mode = .shared
} else {
return nil
} else if #available(macOS 26.0, *) {
if let attachment = virtioConfig.attachment as? VZVmnetNetworkDeviceAttachment {
mode = .host
var status: vmnet_return_t = .VMNET_SUCCESS
guard let vmnetConfig = vmnet_network_copy_serialization(attachment.network, &status), status == .VMNET_SUCCESS else {
return nil
}
if let uuidPtr = xpc_dictionary_get_uuid(vmnetConfig, vmnet.vmnet_network_identifier_key) {
let uuidBytes = UnsafeRawPointer(uuidPtr).assumingMemoryBound(to: UInt8.self)
let uuid = UUID(uuid: (
uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3],
uuidBytes[4], uuidBytes[5], uuidBytes[6], uuidBytes[7],
uuidBytes[8], uuidBytes[9], uuidBytes[10], uuidBytes[11],
uuidBytes[12], uuidBytes[13], uuidBytes[14], uuidBytes[15]
))
hostNetUuid = uuid.uuidString
}
}
}
return nil
}

func vzNetworking() -> VZNetworkDeviceConfiguration? {
Expand Down Expand Up @@ -109,7 +136,24 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
let attachment = VZBridgedNetworkDeviceAttachment(interface: found)
config.attachment = attachment
}
case .host:
if #available(macOS 26.0, *) {
if let netUuid = hostNetUuid {
let vmnetConfig = xpc_dictionary_create_empty()
xpc_dictionary_set_uint64(vmnetConfig, vmnet.vmnet_operation_mode_key, UInt64(vmnet.vmnet_mode_t.VMNET_HOST_MODE.rawValue))
xpc_dictionary_set_uuid(vmnetConfig, vmnet.vmnet_network_identifier_key, netUuid)

var status: vmnet_return_t = .VMNET_SUCCESS
guard let vmnetNetwork = vmnet_network_create_with_serialization(vmnetConfig, &status), status == .VMNET_SUCCESS else {
return nil
}

let attachment = VZVmnetNetworkDeviceAttachment(network: vmnetNetwork)
config.attachment = attachment
}
}
}

return config
}
}
Expand Down
21 changes: 21 additions & 0 deletions Platform/macOS/VMConfigAppleNetworkingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ import SwiftUI
import Virtualization

struct VMConfigAppleNetworkingView: View {
@AppStorage("HostNetworks") var hostNetworksData: Data = Data()
@Binding var config: UTMAppleConfigurationNetwork
@EnvironmentObject private var data: UTMData
@State private var newMacAddress: String?
@State private var hostNetworks: [UTMConfigurationHostNetwork] = []

private func loadData() {
hostNetworks = (try? PropertyListDecoder().decode([UTMConfigurationHostNetwork].self, from: hostNetworksData)) ?? []
}

var body: some View {
Form {
Expand Down Expand Up @@ -50,6 +56,21 @@ struct VMConfigAppleNetworkingView: View {
}
}
}
if #available(macOS 26.0, *) {
if config.mode == .host {
Picker("Host Network", selection: $config.hostNetUuid) {
Text("Default (private)")
.tag(nil as String?)
ForEach(hostNetworks) { interface in
Text(interface.name)
.tag(interface.uuid as String?)
}
}.help("You can configure additional host networks in UTM Settings.")
if config.hostNetUuid != nil {
Text("Note: No DHCP will be provided by UTM")
}
}
}
}
}

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

// MARK: UTMScriptingModifierKey
Expand Down
1 change: 1 addition & 0 deletions Scripting/UTMScriptingConfigImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ extension UTMScriptingConfigImpl {
switch mode {
case .shared: return .shared
case .bridged: return .bridged
case .host: return .host
}
}

Expand Down