diff --git a/.cirrus.yml b/.cirrus.yml index 7d5c84d..79b2a3b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,7 +1,7 @@ use_compute_credits: true env: - XCODE_TAG: 15-beta-2 + XCODE_TAG: 15-beta-5 task: name: Test on Ventura diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 07d68be..d111412 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -91,7 +91,7 @@ struct Run: AsyncParsableCommand { """, discussion: """ Specify "list" as an interface name (--net-bridged=list) to list the available bridged interfaces. """, valueName: "interface name")) - var netBridged: String? + var netBridged: [String] @Flag(help: ArgumentHelp("Use software networking instead of the default shared (NAT) networking", discussion: "Learn how to configure Softnet for use with Tart here: https://github.com/cirruslabs/softnet")) @@ -105,7 +105,7 @@ struct Run: AsyncParsableCommand { throw ValidationError("--vnc and --vnc-experimental are mutually exclusive") } - if netBridged != nil && netSoftnet { + if netBridged.count > 0 && netSoftnet { throw ValidationError("--net-bridged and --net-softnet are mutually exclusive") } @@ -331,23 +331,19 @@ struct Run: AsyncParsableCommand { return try Softnet(vmMACAddress: config.macAddress.string) } - if let netBridged = netBridged { - let matchingInterfaces = VZBridgedNetworkInterface.networkInterfaces.filter { interface in - interface.identifier == netBridged || interface.localizedDisplayName == netBridged + if netBridged.count > 0 { + func findBridgedInterface(_ name: String) throws -> VZBridgedNetworkInterface { + let interface = VZBridgedNetworkInterface.networkInterfaces.first { interface in + interface.identifier == name || interface.localizedDisplayName == name + } + if (interface == nil) { + throw ValidationError("no bridge interfaces matched \"\(netBridged)\", " + + "available interfaces: \(bridgeInterfaces())") + } + return interface! } - if matchingInterfaces.isEmpty { - let available = bridgeInterfaces().joined(separator: ", ") - throw ValidationError("no bridge interfaces matched \"\(netBridged)\", " - + "available interfaces: \(available)") - } - - if matchingInterfaces.count > 1 { - throw ValidationError("more than one bridge interface matched \"\(netBridged)\", " - + "consider refining the search criteria") - } - - return NetworkBridged(interface: matchingInterfaces.first!) + return NetworkBridged(interfaces: try netBridged.map { try findBridgedInterface($0) }) } return nil diff --git a/Sources/tart/Network/Network.swift b/Sources/tart/Network/Network.swift index 7883305..7df6c33 100644 --- a/Sources/tart/Network/Network.swift +++ b/Sources/tart/Network/Network.swift @@ -1,7 +1,7 @@ import Virtualization protocol Network { - func attachment() -> VZNetworkDeviceAttachment + func attachments() -> [VZNetworkDeviceAttachment] func run(_ sema: DispatchSemaphore) throws func stop() async throws } diff --git a/Sources/tart/Network/NetworkBridged.swift b/Sources/tart/Network/NetworkBridged.swift index af6ed25..2fbd4df 100644 --- a/Sources/tart/Network/NetworkBridged.swift +++ b/Sources/tart/Network/NetworkBridged.swift @@ -2,14 +2,14 @@ import Foundation import Virtualization class NetworkBridged: Network { - let interface: VZBridgedNetworkInterface + let interfaces: [VZBridgedNetworkInterface] - init(interface: VZBridgedNetworkInterface) { - self.interface = interface + init(interfaces: [VZBridgedNetworkInterface]) { + self.interfaces = interfaces } - func attachment() -> VZNetworkDeviceAttachment { - VZBridgedNetworkDeviceAttachment(interface: interface) + func attachments() -> [VZNetworkDeviceAttachment] { + interfaces.map { VZBridgedNetworkDeviceAttachment(interface: $0) } } func run(_ sema: DispatchSemaphore) throws { diff --git a/Sources/tart/Network/NetworkShared.swift b/Sources/tart/Network/NetworkShared.swift index 203f92d..c58cc41 100644 --- a/Sources/tart/Network/NetworkShared.swift +++ b/Sources/tart/Network/NetworkShared.swift @@ -2,8 +2,8 @@ import Foundation import Virtualization class NetworkShared: Network { - func attachment() -> VZNetworkDeviceAttachment { - VZNATNetworkDeviceAttachment() + func attachments() -> [VZNetworkDeviceAttachment] { + [VZNATNetworkDeviceAttachment()] } func run(_ sema: DispatchSemaphore) throws { diff --git a/Sources/tart/Network/Softnet.swift b/Sources/tart/Network/Softnet.swift index 770b975..7f1d6ad 100644 --- a/Sources/tart/Network/Softnet.swift +++ b/Sources/tart/Network/Softnet.swift @@ -92,9 +92,9 @@ class Softnet: Network { } } - func attachment() -> VZNetworkDeviceAttachment { + func attachments() -> [VZNetworkDeviceAttachment] { let fh = FileHandle.init(fileDescriptor: vmFD) - return VZFileHandleNetworkDeviceAttachment(fileHandle: fh) + return [VZFileHandleNetworkDeviceAttachment(fileHandle: fh)] } static func configureSUIDBitIfNeeded() throws { diff --git a/Sources/tart/VM.swift b/Sources/tart/VM.swift index a97ee2c..038f03f 100644 --- a/Sources/tart/VM.swift +++ b/Sources/tart/VM.swift @@ -320,10 +320,12 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { } // Networking - let vio = VZVirtioNetworkDeviceConfiguration() - vio.attachment = network.attachment() - vio.macAddress = vmConfig.macAddress - configuration.networkDevices = [vio] + configuration.networkDevices = network.attachments().map { + let vio = VZVirtioNetworkDeviceConfiguration() + vio.attachment = $0 + vio.macAddress = vmConfig.macAddress + return vio + } // Storage var attachments = [try VZDiskImageStorageDeviceAttachment(url: diskURL, readOnly: false)]