mirror of https://github.com/cirruslabs/tart.git
tart run: introduce --net-bridged (#245)
* tart run: introduce --net-bridged * tart.entitlements: add com.apple.vm.networking
This commit is contained in:
parent
678ce0a55a
commit
4e20ea8f72
|
|
@ -4,5 +4,7 @@
|
|||
<dict>
|
||||
<key>com.apple.security.virtualization</key>
|
||||
<true/>
|
||||
<key>com.apple.vm.networking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -54,10 +54,21 @@ struct Run: AsyncParsableCommand {
|
|||
""", valueName: "name:path[:ro]"))
|
||||
var dir: [String] = []
|
||||
|
||||
@Option(help: ArgumentHelp("""
|
||||
Use bridged networking instead of the default shared (NAT) networking \n(e.g. --net-bridged=en0 or --net-bridged=\"Wi-Fi\")
|
||||
""", discussion: """
|
||||
Specify "list" as an interface name (--net-bridged=list) to list the available bridged interfaces.
|
||||
""", valueName: "interface name"))
|
||||
var netBridged: String?
|
||||
|
||||
func validate() throws {
|
||||
if vnc && vncExperimental {
|
||||
throw ValidationError("--vnc and --vnc-experimental are mutually exclusive")
|
||||
}
|
||||
|
||||
if withSoftnet && netBridged != nil {
|
||||
throw ValidationError("--with-softnet and --net-bridged are mutually exclusive")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
|
@ -65,7 +76,7 @@ struct Run: AsyncParsableCommand {
|
|||
let vmDir = try VMStorageLocal().open(name)
|
||||
vm = try VM(
|
||||
vmDir: vmDir,
|
||||
withSoftnet: withSoftnet,
|
||||
network: userSpecifiedNetwork(vmDir: vmDir) ?? NetworkShared(),
|
||||
additionalDiskAttachments: additionalDiskAttachments(),
|
||||
directoryShares: directoryShares()
|
||||
)
|
||||
|
|
@ -125,6 +136,47 @@ struct Run: AsyncParsableCommand {
|
|||
}
|
||||
}
|
||||
|
||||
func userSpecifiedNetwork(vmDir: VMDirectory) throws -> Network? {
|
||||
if withSoftnet {
|
||||
let config = try VMConfig.init(fromURL: vmDir.configURL)
|
||||
|
||||
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 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 nil
|
||||
}
|
||||
|
||||
func bridgeInterfaces() -> [String] {
|
||||
VZBridgedNetworkInterface.networkInterfaces.map { interface in
|
||||
var bridgeDescription = interface.identifier
|
||||
|
||||
if let localizedDisplayName = interface.localizedDisplayName {
|
||||
bridgeDescription += " (or \"\(localizedDisplayName)\")"
|
||||
}
|
||||
|
||||
return bridgeDescription
|
||||
}
|
||||
}
|
||||
|
||||
func additionalDiskAttachments() throws -> [VZDiskImageStorageDeviceAttachment] {
|
||||
var result: [VZDiskImageStorageDeviceAttachment] = []
|
||||
let readOnlySuffix = ":ro"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import Virtualization
|
||||
|
||||
protocol Network {
|
||||
func attachment() -> VZNetworkDeviceAttachment
|
||||
func run() throws
|
||||
func stop() throws
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
import Virtualization
|
||||
|
||||
class NetworkBridged: Network {
|
||||
let interface: VZBridgedNetworkInterface
|
||||
|
||||
init(interface: VZBridgedNetworkInterface) {
|
||||
self.interface = interface
|
||||
}
|
||||
|
||||
func attachment() -> VZNetworkDeviceAttachment {
|
||||
VZBridgedNetworkDeviceAttachment(interface: interface)
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
|
||||
func stop() throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
import Virtualization
|
||||
|
||||
class NetworkShared: Network {
|
||||
func attachment() -> VZNetworkDeviceAttachment {
|
||||
VZNATNetworkDeviceAttachment()
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
|
||||
func stop() throws {
|
||||
// no-op, only used for Softnet
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import Foundation
|
||||
import Virtualization
|
||||
|
||||
enum SoftnetError: Error {
|
||||
case InitializationFailed(why: String)
|
||||
}
|
||||
|
||||
class Softnet {
|
||||
class Softnet: Network {
|
||||
private let process = Process()
|
||||
|
||||
let vmFD: Int32
|
||||
|
|
@ -57,4 +58,9 @@ class Softnet {
|
|||
throw SoftnetError.InitializationFailed(why: "setsockopt(SO_SNDBUF) returned \(ret)")
|
||||
}
|
||||
}
|
||||
|
||||
func attachment() -> VZNetworkDeviceAttachment {
|
||||
let fh = FileHandle.init(fileDescriptor: vmFD)
|
||||
return VZFileHandleNetworkDeviceAttachment(fileHandle: fh)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,10 +34,10 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
// VM's config
|
||||
var config: VMConfig
|
||||
|
||||
var softnet: Softnet? = nil
|
||||
var network: Network
|
||||
|
||||
init(vmDir: VMDirectory,
|
||||
withSoftnet: Bool = false,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = [],
|
||||
directoryShares: [DirectoryShare] = []
|
||||
) throws {
|
||||
|
|
@ -49,13 +49,10 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
// Initialize the virtual machine and its configuration
|
||||
if withSoftnet {
|
||||
softnet = try Softnet(vmMACAddress: config.macAddress.string)
|
||||
}
|
||||
|
||||
self.network = network
|
||||
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL,
|
||||
nvramURL: vmDir.nvramURL, vmConfig: config,
|
||||
softnet: softnet, additionalDiskAttachments: additionalDiskAttachments,
|
||||
network: network, additionalDiskAttachments: additionalDiskAttachments,
|
||||
directoryShares: directoryShares)
|
||||
virtualMachine = VZVirtualMachine(configuration: configuration)
|
||||
|
||||
|
|
@ -114,7 +111,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
vmDir: VMDirectory,
|
||||
ipswURL: URL?,
|
||||
diskSizeGB: UInt16,
|
||||
withSoftnet: Bool = false,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = []
|
||||
) async throws {
|
||||
let ipswURL = ipswURL != nil ? ipswURL! : try await VM.retrieveLatestIPSW();
|
||||
|
|
@ -149,12 +146,9 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
try config.save(toURL: vmDir.configURL)
|
||||
|
||||
// Initialize the virtual machine and its configuration
|
||||
if withSoftnet {
|
||||
softnet = try Softnet(vmMACAddress: config.macAddress.string)
|
||||
}
|
||||
|
||||
self.network = network
|
||||
let configuration = try Self.craftConfiguration(diskURL: vmDir.diskURL, nvramURL: vmDir.nvramURL,
|
||||
vmConfig: config, softnet: softnet,
|
||||
vmConfig: config, network: network,
|
||||
additionalDiskAttachments: additionalDiskAttachments,
|
||||
directoryShares: [])
|
||||
virtualMachine = VZVirtualMachine(configuration: configuration)
|
||||
|
|
@ -193,9 +187,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
func run(_ recovery: Bool) async throws {
|
||||
if let softnet = softnet {
|
||||
try softnet.run()
|
||||
}
|
||||
try network.run()
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
Task {
|
||||
|
|
@ -225,16 +217,14 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
if let softnet = softnet {
|
||||
try softnet.stop();
|
||||
}
|
||||
try network.stop()
|
||||
}
|
||||
|
||||
static func craftConfiguration(
|
||||
diskURL: URL,
|
||||
nvramURL: URL,
|
||||
vmConfig: VMConfig,
|
||||
softnet: Softnet? = nil,
|
||||
network: Network = NetworkShared(),
|
||||
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment],
|
||||
directoryShares: [DirectoryShare]
|
||||
) throws -> VZVirtualMachineConfiguration {
|
||||
|
|
@ -268,13 +258,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
|
|||
|
||||
// Networking
|
||||
let vio = VZVirtioNetworkDeviceConfiguration()
|
||||
|
||||
if let softnet = softnet {
|
||||
let fh = FileHandle.init(fileDescriptor: softnet.vmFD)
|
||||
vio.attachment = VZFileHandleNetworkDeviceAttachment(fileHandle: fh)
|
||||
} else {
|
||||
vio.attachment = VZNATNetworkDeviceAttachment()
|
||||
}
|
||||
vio.attachment = network.attachment()
|
||||
vio.macAddress = vmConfig.macAddress
|
||||
configuration.networkDevices = [vio]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue