Skip to content

Commit 44c2773

Browse files
committed
Add support for host networking with Apple Virtualization
1 parent b61dc2b commit 44c2773

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

Configuration/UTMAppleConfigurationNetwork.swift

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
2323
enum NetworkMode: String, CaseIterable, QEMUConstant {
2424
case shared = "Shared"
2525
case bridged = "Bridged"
26+
case host = "Host"
2627

2728
var prettyValue: String {
2829
switch self {
2930
case .shared: return NSLocalizedString("Shared Network", comment: "UTMAppleConfigurationNetwork")
3031
case .bridged: return NSLocalizedString("Bridged (Advanced)", comment: "UTMAppleConfigurationNetwork")
32+
case .host: return NSLocalizedString("Host (Advanced)", comment: "UTMAppleConfigurationNetwork")
3133
}
3234
}
3335
}
@@ -40,12 +42,16 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
4042
/// In bridged mode this is the physical interface to bridge.
4143
var bridgeInterface: String?
4244

45+
/// Network UUID to attach to in host mode
46+
var hostNetUuid: String?
47+
4348
let id = UUID()
4449

4550
enum CodingKeys: String, CodingKey {
4651
case mode = "Mode"
4752
case macAddress = "MacAddress"
4853
case bridgeInterface = "BridgeInterface"
54+
case hostNetUuid = "HostNetUuid"
4955
}
5056

5157
init() {
@@ -56,6 +62,7 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
5662
mode = try values.decode(NetworkMode.self, forKey: .mode)
5763
macAddress = try values.decode(String.self, forKey: .macAddress)
5864
bridgeInterface = try values.decodeIfPresent(String.self, forKey: .bridgeInterface)
65+
hostNetUuid = try values.decodeIfPresent(UUID.self, forKey: .hostNetUuid)?.uuidString
5966
}
6067

6168
func encode(to encoder: Encoder) throws {
@@ -65,6 +72,9 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
6572
if mode == .bridged {
6673
try container.encodeIfPresent(bridgeInterface, forKey: .bridgeInterface)
6774
}
75+
if mode == .host {
76+
try container.encodeIfPresent(hostNetUuid, forKey: .hostNetUuid)
77+
}
6878
}
6979

7080
init?(from config: VZNetworkDeviceConfiguration) {
@@ -77,9 +87,26 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
7787
bridgeInterface = attachment.interface.identifier
7888
} else if let _ = virtioConfig.attachment as? VZNATNetworkDeviceAttachment {
7989
mode = .shared
80-
} else {
81-
return nil
90+
} else if #available(macOS 26.0, *) {
91+
if let attachment = virtioConfig.attachment as? VZVmnetNetworkDeviceAttachment {
92+
mode = .host
93+
var status: vmnet_return_t = .VMNET_SUCCESS
94+
guard let vmnetConfig = vmnet_network_copy_serialization(attachment.network, &status), status == .VMNET_SUCCESS else {
95+
return nil
96+
}
97+
if let uuidPtr = xpc_dictionary_get_uuid(vmnetConfig, vmnet.vmnet_network_identifier_key) {
98+
let uuidBytes = UnsafeRawPointer(uuidPtr).assumingMemoryBound(to: UInt8.self)
99+
let uuid = UUID(uuid: (
100+
uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3],
101+
uuidBytes[4], uuidBytes[5], uuidBytes[6], uuidBytes[7],
102+
uuidBytes[8], uuidBytes[9], uuidBytes[10], uuidBytes[11],
103+
uuidBytes[12], uuidBytes[13], uuidBytes[14], uuidBytes[15]
104+
))
105+
hostNetUuid = uuid.uuidString
106+
}
107+
}
82108
}
109+
return nil
83110
}
84111

85112
func vzNetworking() -> VZNetworkDeviceConfiguration? {
@@ -109,7 +136,24 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
109136
let attachment = VZBridgedNetworkDeviceAttachment(interface: found)
110137
config.attachment = attachment
111138
}
139+
case .host:
140+
if #available(macOS 26.0, *) {
141+
if let netUuid = hostNetUuid {
142+
let vmnetConfig = xpc_dictionary_create_empty()
143+
xpc_dictionary_set_uint64(vmnetConfig, vmnet.vmnet_operation_mode_key, UInt64(vmnet.vmnet_mode_t.VMNET_HOST_MODE.rawValue))
144+
xpc_dictionary_set_uuid(vmnetConfig, vmnet.vmnet_network_identifier_key, netUuid)
145+
146+
var status: vmnet_return_t = .VMNET_SUCCESS
147+
guard let vmnetNetwork = vmnet_network_create_with_serialization(vmnetConfig, &status), status == .VMNET_SUCCESS else {
148+
return nil
149+
}
150+
151+
let attachment = VZVmnetNetworkDeviceAttachment(network: vmnetNetwork)
152+
config.attachment = attachment
153+
}
154+
}
112155
}
156+
113157
return config
114158
}
115159
}

Platform/macOS/VMConfigAppleNetworkingView.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ import SwiftUI
1818
import Virtualization
1919

2020
struct VMConfigAppleNetworkingView: View {
21+
@AppStorage("HostNetworks") var hostNetworksData: Data = Data()
2122
@Binding var config: UTMAppleConfigurationNetwork
2223
@EnvironmentObject private var data: UTMData
2324
@State private var newMacAddress: String?
25+
@State private var hostNetworks: [UTMConfigurationHostNetwork] = []
26+
27+
private func loadData() {
28+
hostNetworks = (try? PropertyListDecoder().decode([UTMConfigurationHostNetwork].self, from: hostNetworksData)) ?? []
29+
}
2430

2531
var body: some View {
2632
Form {
@@ -50,6 +56,21 @@ struct VMConfigAppleNetworkingView: View {
5056
}
5157
}
5258
}
59+
if #available(macOS 26.0, *) {
60+
if config.mode == .host {
61+
Picker("Host Network", selection: $config.hostNetUuid) {
62+
Text("Default (private)")
63+
.tag(nil as String?)
64+
ForEach(hostNetworks) { interface in
65+
Text(interface.name)
66+
.tag(interface.uuid as String?)
67+
}
68+
}.help("You can configure additional host networks in UTM Settings.")
69+
if config.hostNetUuid != nil {
70+
Text("Note: No DHCP will be provided by UTM")
71+
}
72+
}
73+
}
5374
}
5475
}
5576

Scripting/UTMScripting.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ import ScriptingBridge
142142
@objc public enum UTMScriptingAppleNetworkMode : AEKeyword {
143143
case shared = 0x53685264 /* 'ShRd' */
144144
case bridged = 0x42724764 /* 'BrGd' */
145+
case host = 0x486f5374 /* 'HoSt' */
145146
}
146147

147148
// MARK: UTMScriptingModifierKey

Scripting/UTMScriptingConfigImpl.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ extension UTMScriptingConfigImpl {
255255
switch mode {
256256
case .shared: return .shared
257257
case .bridged: return .bridged
258+
case .host: return .host
258259
}
259260
}
260261

0 commit comments

Comments
 (0)