Files
UTM/Platform/macOS/VMConfigAppleNetworkingView.swift
T
osy 47eadda150 apple: make the vmnet modes predictable and simpler
Follow-up to the vmnet networking modes:

- Rename "Shared Network (VM to VM)" to "NAT Network" (config value
  "NATNetwork", scripting term "nat network") so the picker does not offer
  two "Shared" modes that differ only by a suffix.
- Request a fixed subnet per mode outside of the ranges vmnet hands out by
  default. Those ranges are also used by NAT attachments and QEMU, so guest
  addresses depended on what else was running whenever the network was
  (re)created. Fall back to a vmnet chosen subnet when ours is taken.
- Show the mode description with DetailedSection. The bare Text row widened
  the form and clipped the labels and controls in the settings window.
- Only require macOS 26 when a network is actually created, so a VM using
  one of these modes can still be edited and saved on older hosts.
- Do not apply locale digit grouping to the vmnet error code.
- Fold the validation flag into the existing one that skipped drives and
  leave the device unattached instead of using a NAT placeholder, remove
  the unused reverse mapping, key the networks by vmnet_mode_t, and isolate
  the manager to the main actor in place of a lock with no concurrent users.

Use .1 as the host-side address within each preferred /24. As reproduced
in PR #7865, vmnet accepts .0 during network creation but the interface
fails later at startup.

Assisted-by: Claude:claude-fable-5-1
Assisted-by: ChatGPT:GPT-6-Astra-Pro
2026-09-18 20:43:49 -04:00

91 lines
3.4 KiB
Swift

//
// Copyright © 2021 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 SwiftUI
import Virtualization
struct VMConfigAppleNetworkingView: View {
@Binding var config: UTMAppleConfigurationNetwork
@EnvironmentObject private var data: UTMData
@State private var newMacAddress: String?
var body: some View {
Form {
if let modeDescription = modeDescription {
DetailedSection("", description: modeDescription) {
VMConfigConstantPicker("Network Mode", selection: $config.mode)
}
} else {
VMConfigConstantPicker("Network Mode", selection: $config.mode)
}
HStack {
TextField("MAC Address", text: $newMacAddress.bound, onCommit: {
commitMacAddress()
})
.onAppear {
newMacAddress = config.macAddress
}
Button("Random") {
let random = VZMACAddress.randomLocallyAdministered().string
newMacAddress = random
commitMacAddress()
}
}
if config.mode == .bridged {
Section(header: Text("Bridged Settings")) {
Picker("Interface", selection: $config.bridgeInterface) {
Text("Automatic")
.tag(nil as String?)
ForEach(VZBridgedNetworkInterface.networkInterfaces, id: \.identifier) { interface in
Text(interface.localizedDisplayName.map { "\($0) (\(interface.identifier))" } ?? interface.identifier)
.tag(interface.identifier as String?)
}
}
}
}
}
}
private var modeDescription: LocalizedStringKey? {
switch config.mode {
case .natNetwork: return "Virtual machines using this mode in the same UTM app can connect to each other and access the internet through your Mac."
case .host: return "Virtual machines using this mode in the same UTM app can connect to each other and your Mac. This network does not provide internet access."
default: return nil
}
}
private func commitMacAddress() {
guard let macAddress = newMacAddress else {
return
}
if let _ = VZMACAddress(string: macAddress) {
config.macAddress = macAddress
} else {
data.busyWork {
throw NSLocalizedString("Invalid MAC address.", comment: "VMConfigAppleNetworkingView")
}
}
}
}
struct VMConfigAppleNetworkingView_Previews: PreviewProvider {
@State static private var config = UTMAppleConfigurationNetwork()
static var previews: some View {
VMConfigAppleNetworkingView(config: $config)
}
}