Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for external/multiple networks in vmnet host mode #6186

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
124 changes: 124 additions & 0 deletions Configuration/UTMConfigurationHostNetwork.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// Copyright © 2024 osy. 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
//
// http://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.
//

import Foundation

/// Host network settings.
struct UTMConfigurationHostNetwork: Codable, Identifiable {
/// Network name
var name: String

/// Network UUID
var uuid: String = UUID().uuidString

let id = UUID()

enum CodingKeys: String, CodingKey {
case name = "Name"
case uuid = "Uuid"
}

init() {
self.name = uuid
}

init(name: String) {
self.name = name
}

init(name: String, uuid: String) {
self.name = name
self.uuid = uuid
}

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
uuid = try values.decodeIfPresent(UUID.self, forKey: .uuid)?.uuidString ?? UUID().uuidString
name = try values.decodeIfPresent(String.self, forKey: .name) ?? uuid
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(uuid, forKey: .uuid)
}

static func parseVMware(from url: URL) -> [UTMConfigurationHostNetwork] {
let accessing = url.startAccessingSecurityScopedResource()
if !accessing { return [] }
defer {
if accessing {
url.stopAccessingSecurityScopedResource()
}
}

var currentId: String?;
var currentName: String?;
var currentUuid: String?;
var result: [UTMConfigurationHostNetwork] = []

if let content = try? String(contentsOf: url) {
for line in content.split(whereSeparator: \.isNewline) {
let parts = line.split(separator: " ")
if parts.count != 3 || (parts[0] != "answer" && !parts[1].starts(with: "VNET_")) {
continue
}

let name_parts = parts[1].split(separator: "_", maxSplits: 2)
if name_parts.count != 3 {
continue
}

if currentId == nil {
currentId = String(name_parts[1])
}

if let id = currentId {
if id != name_parts[1] {
if let uuid = currentUuid {
result.append(UTMConfigurationHostNetwork(name: currentName ?? "VMware vmnet\(id)", uuid: uuid))
}

currentId = String(name_parts[1])
currentName = nil
currentUuid = nil
}

if name_parts[2] == "DISPLAY_NAME" {
currentName = String(parts[2])
}

if name_parts[2] == "HOSTONLY_UUID" {
currentUuid = String(parts[2])
}
}
}

if let id = currentId, let uuid = currentUuid {
var newNetwork = UTMConfigurationHostNetwork()
newNetwork.name = if let name = currentName {
name
} else {
"VMware vmnet\(id)"
}
newNetwork.uuid = uuid
result.append(newNetwork)
}
}

return result
}
}
3 changes: 3 additions & 0 deletions Configuration/UTMQemuConfiguration+Arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,9 @@ import Virtualization // for getting network interfaces
useVMnet = true
"vmnet-host"
"id=net\(i)"
if let netUuid = networks[i].hostNetUuid {
"net-uuid=\(netUuid)"
}
} else {
"user"
"id=net\(i)"
Expand Down
8 changes: 8 additions & 0 deletions Configuration/UTMQemuConfigurationNetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ struct UTMQemuConfigurationNetwork: Codable, Identifiable {
/// DNS search domain for emulated VLAN.
var vlanDnsSearchDomain: String?

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

let id = UUID()

/// Generate a random MAC address
Expand Down Expand Up @@ -99,6 +102,7 @@ struct UTMQemuConfigurationNetwork: Codable, Identifiable {
case vlanDnsServerAddress = "VlanDnsServerAddress"
case vlanDnsServerAddressIPv6 = "VlanDnsServerAddressIPv6"
case vlanDnsSearchDomain = "VlanDnsSearchDomain"
case hostNetUuid = "HostNetUuid"
}

init() {
Expand All @@ -122,6 +126,7 @@ struct UTMQemuConfigurationNetwork: Codable, Identifiable {
vlanDnsServerAddress = try values.decodeIfPresent(String.self, forKey: .vlanDnsServerAddress)
vlanDnsServerAddressIPv6 = try values.decodeIfPresent(String.self, forKey: .vlanDnsServerAddressIPv6)
vlanDnsSearchDomain = try values.decodeIfPresent(String.self, forKey: .vlanDnsSearchDomain)
hostNetUuid = try values.decodeIfPresent(UUID.self, forKey: .hostNetUuid)?.uuidString
}

func encode(to encoder: Encoder) throws {
Expand All @@ -144,6 +149,9 @@ struct UTMQemuConfigurationNetwork: Codable, Identifiable {
try container.encodeIfPresent(vlanDnsServerAddress, forKey: .vlanDnsServerAddress)
try container.encodeIfPresent(vlanDnsServerAddressIPv6, forKey: .vlanDnsServerAddressIPv6)
try container.encodeIfPresent(vlanDnsSearchDomain, forKey: .vlanDnsSearchDomain)
if mode == .host {
try container.encodeIfPresent(hostNetUuid, forKey: .hostNetUuid)
}
}
}

Expand Down
21 changes: 20 additions & 1 deletion Platform/Shared/VMConfigNetworkView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ import Virtualization
#endif

struct VMConfigNetworkView: View {
@AppStorage("HostNetworks") var hostNetworksData: Data = Data()
@Binding var config: UTMQemuConfigurationNetwork
@Binding var system: UTMQemuConfigurationSystem
@State private var hostNetworks: [UTMConfigurationHostNetwork] = []
@State private var showAdvanced: Bool = false

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

var body: some View {
VStack {
Form {
Expand All @@ -40,9 +46,22 @@ struct VMConfigNetworkView: View {
}
}
}
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?)
}
}
if config.hostNetUuid != nil {
Text("Note: No DHCP will be provided by UTM")
}
}
#endif
VMConfigConstantPicker("Emulated Network Card", selection: $config.hardware, type: system.architecture.networkDeviceType)
}
}.onAppear(perform: loadData)

HStack {
DefaultTextField("MAC Address", text: $config.macAddress, prompt: "00:00:00:00:00:00")
Expand Down
83 changes: 83 additions & 0 deletions Platform/macOS/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ struct SettingsView: View {
.tabItem {
Label("Input", systemImage: "keyboard")
}
if #available(macOS 12, *) {
NetworkSettingsView().padding()
.tabItem {
Label("Network", systemImage: "network")
}
}
ServerSettingsView().padding()
.tabItem {
Label("Server", systemImage: "server.rack")
Expand Down Expand Up @@ -185,6 +191,83 @@ struct InputSettingsView: View {
}
}

@available(macOS 12, *)
struct NetworkSettingsView: View {
@AppStorage("HostNetworks") var hostNetworksData: Data = Data()
@State private var hostNetworks: [UTMConfigurationHostNetwork] = []
@State private var selectedID: UUID?
@State private var isImporterPresented: Bool = false

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

private func saveData() {
hostNetworksData = (try? PropertyListEncoder().encode(hostNetworks)) ?? Data()
}

var body: some View {
Form {
Section(header: Text("Host networks")) {
Table($hostNetworks, selection: $selectedID) {
TableColumn("Name") { $network in
TextField(
"Name",
text: $network.name
)
.labelsHidden()
}
TableColumn("UUID") { $network in
TextField(
"UUID",
text: $network.uuid,
onEditingChanged: { (editingChanged) in
if !editingChanged && UUID(uuidString: network.uuid) != nil {
saveData()
}
}
)
.labelsHidden()
.autocorrectionDisabled()
.foregroundStyle(UUID(uuidString: network.uuid) == nil ? .red : .primary)
}
.width(min: 160)
}
HStack {
Button("Import from VMware Fusion") {
isImporterPresented.toggle()
}.fileImporter(isPresented: $isImporterPresented, allowedContentTypes: [.data]) { result in

if let url = try? result.get() {
for network in UTMConfigurationHostNetwork.parseVMware(from: url) {
if !hostNetworks.contains(where: {$0.uuid == network.uuid}) {
hostNetworks.append(network)
}
}

saveData()
}
}.help("Navigate to `/Library/Preferences/VMware Fusion` (⌘+Shift+G) and select the `networking` file")
Spacer()
Button("Delete") {
hostNetworks.removeAll { network in
network.id == selectedID
}
selectedID = nil
saveData()

}.disabled(selectedID == nil)
Button("Add") {
let network = UTMConfigurationHostNetwork(name: "Network \(hostNetworks.count)")
hostNetworks.append(network)
saveData()
}
}
}
}.onAppear(perform: loadData)
}
}

struct ServerSettingsView: View {
private let defaultPort = 21589

Expand Down
10 changes: 10 additions & 0 deletions UTM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
03FA9C722B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; };
03FA9C732B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; };
03FA9C742B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; };
03FA9C752B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */; };
2C33B3A92566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; };
2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; };
2C6D9E03256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6D9E02256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift */; };
Expand Down Expand Up @@ -1579,6 +1583,7 @@
037DAA202B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
037DAA212B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
037DAA222B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMConfigurationHostNetwork.swift; sourceTree = "<group>"; };
2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMContextMenuModifier.swift; sourceTree = "<group>"; };
2C6D9E02256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayQemuTerminalWindowController.swift; sourceTree = "<group>"; };
4B224B9C279D4D8100B63CFF /* InListButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InListButtonStyle.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2699,6 +2704,7 @@
841619A9284315F9000034B2 /* UTMConfigurationInfo.swift */,
843BF83728451B380029D60D /* UTMConfigurationTerminal.swift */,
848D99BB28636AC90055C215 /* UTMConfigurationDrive.swift */,
03FA9C712B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift */,
848A98AF286A0F74006F0550 /* UTMAppleConfiguration.swift */,
848A98BF286A20E3006F0550 /* UTMAppleConfigurationBoot.swift */,
848A98B1286A0FDE006F0550 /* UTMAppleConfigurationSystem.swift */,
Expand Down Expand Up @@ -3516,6 +3522,7 @@
CE2D92AA24AD46670059923A /* UTMSpiceIO.m in Sources */,
84909A9127CADAE0005605F1 /* UTMUnavailableVMView.swift in Sources */,
CE2D958524AD4F990059923A /* VMDrivesSettingsView.swift in Sources */,
03FA9C722B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
848D99BC28636AC90055C215 /* UTMConfigurationDrive.swift in Sources */,
CED814E924C79F070042F0F1 /* VMConfigDriveCreateView.swift in Sources */,
842B9F8D28CC58B700031EE7 /* UTMPatches.swift in Sources */,
Expand Down Expand Up @@ -3797,6 +3804,7 @@
847BF9AC2A49C783000BD9AA /* VMData.swift in Sources */,
CE25124729BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift in Sources */,
CE2D958824AD4F990059923A /* VMConfigPortForwardForm.swift in Sources */,
03FA9C752B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
845F170D289CB3DE00944904 /* VMDisplayTerminal.swift in Sources */,
84C4D9042880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */,
CEBE820526A4C1B5007AAB12 /* VMWizardDrivesView.swift in Sources */,
Expand Down Expand Up @@ -3883,6 +3891,7 @@
8401865F2887B1620050AC51 /* VMDisplayTerminalViewController.swift in Sources */,
CEA45E8F263519B5002FA97D /* VMContextMenuModifier.swift in Sources */,
85EC516527CC8D0F004A51DE /* VMConfigAdvancedNetworkView.swift in Sources */,
03FA9C732B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
CEA45E91263519B5002FA97D /* VMDisplayMetalViewController+Pencil.m in Sources */,
CEA45E94263519B5002FA97D /* UTMLegacyQemuConfiguration+Drives.m in Sources */,
848A98C5286F332D006F0550 /* UTMConfiguration.swift in Sources */,
Expand Down Expand Up @@ -4069,6 +4078,7 @@
CEF7F5E72AEEDCC400E34952 /* UTMRegistry.swift in Sources */,
CEF7F5E82AEEDCC400E34952 /* VMDisplayViewControllerDelegate.swift in Sources */,
CEF7F5EA2AEEDCC400E34952 /* VMConfigConstantPicker.swift in Sources */,
03FA9C742B9BBDB000C53A5A /* UTMConfigurationHostNetwork.swift in Sources */,
CEF7F5EC2AEEDCC400E34952 /* VMToolbarModifier.swift in Sources */,
CEF7F5ED2AEEDCC400E34952 /* VMCursor.m in Sources */,
CEF7F5EE2AEEDCC400E34952 /* VMConfigDriveDetailsView.swift in Sources */,
Expand Down
Loading