mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	updates tailscale/corp#24197 tailmac run now supports the --share option which will allow you to specify a directory on the host which can be mounted in the guest using mount_virtiofs vmshare <path>. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
		
			
				
	
	
		
			159 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| import Foundation
 | |
| import Virtualization
 | |
| 
 | |
| struct TailMacConfigHelper {
 | |
|     let config: Config
 | |
| 
 | |
|     func computeCPUCount() -> Int {
 | |
|         let totalAvailableCPUs = ProcessInfo.processInfo.processorCount
 | |
| 
 | |
|         var virtualCPUCount = totalAvailableCPUs <= 1 ? 1 : totalAvailableCPUs - 1
 | |
|         virtualCPUCount = max(virtualCPUCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
 | |
|         virtualCPUCount = min(virtualCPUCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount)
 | |
| 
 | |
|         return virtualCPUCount
 | |
|     }
 | |
| 
 | |
|     func computeMemorySize() -> UInt64 {
 | |
|         // Set the amount of system memory to 4 GB; this is a baseline value
 | |
|         // that you can change depending on your use case.
 | |
|         var memorySize = config.memorySize
 | |
|         memorySize = max(memorySize, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
 | |
|         memorySize = min(memorySize, VZVirtualMachineConfiguration.maximumAllowedMemorySize)
 | |
| 
 | |
|         return memorySize
 | |
|     }
 | |
| 
 | |
|     func createBootLoader() -> VZMacOSBootLoader {
 | |
|         return VZMacOSBootLoader()
 | |
|     }
 | |
| 
 | |
|     func createGraphicsDeviceConfiguration() -> VZMacGraphicsDeviceConfiguration {
 | |
|         let graphicsConfiguration = VZMacGraphicsDeviceConfiguration()
 | |
|         graphicsConfiguration.displays = [
 | |
|             // The system arbitrarily chooses the resolution of the display to be 1920 x 1200.
 | |
|             VZMacGraphicsDisplayConfiguration(widthInPixels: 1920, heightInPixels: 1200, pixelsPerInch: 80)
 | |
|         ]
 | |
| 
 | |
|         return graphicsConfiguration
 | |
|     }
 | |
| 
 | |
|     func createBlockDeviceConfiguration() -> VZVirtioBlockDeviceConfiguration {
 | |
|         do {
 | |
|             let diskImageAttachment = try VZDiskImageStorageDeviceAttachment(url: config.diskImageURL, readOnly: false)
 | |
|             let disk = VZVirtioBlockDeviceConfiguration(attachment: diskImageAttachment)
 | |
|             return disk
 | |
|         } catch {
 | |
|             fatalError("Failed to create Disk image. \(error)")
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     func createSocketDeviceConfiguration() -> VZVirtioSocketDeviceConfiguration {
 | |
|        return VZVirtioSocketDeviceConfiguration()
 | |
|     }
 | |
| 
 | |
|     func createNetworkDeviceConfiguration() -> VZVirtioNetworkDeviceConfiguration {
 | |
|         let networkDevice = VZVirtioNetworkDeviceConfiguration()
 | |
|         networkDevice.macAddress = VZMACAddress(string: config.ethermac)!
 | |
| 
 | |
|         /* Bridged networking requires special entitlements from Apple
 | |
|          if let interface = VZBridgedNetworkInterface.networkInterfaces.first(where: { $0.identifier == "en0" }) {
 | |
|             let networkAttachment = VZBridgedNetworkDeviceAttachment(interface: interface)
 | |
|             networkDevice.attachment = networkAttachment
 | |
|          } else {
 | |
|             print("Assuming en0 for bridged ethernet.  Could not findd adapter")
 | |
|          }*/
 | |
| 
 | |
|         /// But we can do NAT without Tim Apple's approval
 | |
|         let networkAttachment = VZNATNetworkDeviceAttachment()
 | |
|         networkDevice.attachment = networkAttachment
 | |
| 
 | |
|         return networkDevice
 | |
|     }
 | |
| 
 | |
|     func createSocketNetworkDeviceConfiguration() -> VZVirtioNetworkDeviceConfiguration {
 | |
|         let networkDevice = VZVirtioNetworkDeviceConfiguration()
 | |
|         networkDevice.macAddress = VZMACAddress(string: config.mac)!
 | |
| 
 | |
|         let socket = Darwin.socket(AF_UNIX, SOCK_DGRAM, 0)
 | |
| 
 | |
|         // Outbound network packets
 | |
|         let serverSocket = config.serverSocket
 | |
| 
 | |
|         // Inbound network packets
 | |
|         let clientSockId = config.vmID
 | |
|         let clientSocket = "/tmp/qemu-dgram-\(clientSockId).sock"
 | |
| 
 | |
|         unlink(clientSocket)
 | |
|         var clientAddr = sockaddr_un()
 | |
|         clientAddr.sun_family = sa_family_t(AF_UNIX)
 | |
|         clientSocket.withCString { ptr in
 | |
|             withUnsafeMutablePointer(to: &clientAddr.sun_path.0) { dest in
 | |
|                 _ = strcpy(dest, ptr)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let bindRes = Darwin.bind(socket,
 | |
|                                   withUnsafePointer(to: &clientAddr, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { $0 } }),
 | |
|                                   socklen_t(MemoryLayout<sockaddr_un>.size))
 | |
| 
 | |
|         if bindRes == -1 {
 | |
|             print("Error binding virtual network client socket - \(String(cString: strerror(errno)))")
 | |
|             return networkDevice
 | |
|         }
 | |
| 
 | |
|         var serverAddr = sockaddr_un()
 | |
|         serverAddr.sun_family = sa_family_t(AF_UNIX)
 | |
|         serverSocket.withCString { ptr in
 | |
|             withUnsafeMutablePointer(to: &serverAddr.sun_path.0) { dest in
 | |
|                 _ = strcpy(dest, ptr)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let connectRes = Darwin.connect(socket,
 | |
|                                         withUnsafePointer(to: &serverAddr, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { $0 } }),
 | |
|                                         socklen_t(MemoryLayout<sockaddr_un>.size))
 | |
| 
 | |
|         if connectRes == -1 {
 | |
|             print("Error binding virtual network server socket - \(String(cString: strerror(errno)))")
 | |
|             return networkDevice
 | |
|         }
 | |
| 
 | |
|         print("Virtual if mac address is \(config.mac)")
 | |
|         print("Client bound to \(clientSocket)")
 | |
|         print("Connected to server at \(serverSocket)")
 | |
|         print("Socket fd is \(socket)")
 | |
| 
 | |
| 
 | |
|         let handle = FileHandle(fileDescriptor: socket)
 | |
|         let device = VZFileHandleNetworkDeviceAttachment(fileHandle: handle)
 | |
|         networkDevice.attachment = device
 | |
|         return networkDevice
 | |
|     }
 | |
| 
 | |
|     func createPointingDeviceConfiguration() -> VZPointingDeviceConfiguration {
 | |
|         return VZMacTrackpadConfiguration()
 | |
|     }
 | |
| 
 | |
|     func createKeyboardConfiguration() -> VZKeyboardConfiguration {
 | |
|         return VZMacKeyboardConfiguration()
 | |
|     }
 | |
| 
 | |
|     func createDirectoryShareConfiguration(tag: String) -> VZDirectorySharingDeviceConfiguration? {
 | |
|         guard let dir = config.sharedDir else { return nil }
 | |
| 
 | |
|         let sharedDir = VZSharedDirectory(url: URL(fileURLWithPath: dir), readOnly: false)
 | |
|         let share = VZSingleDirectoryShare(directory: sharedDir)
 | |
| 
 | |
|         // Create the VZVirtioFileSystemDeviceConfiguration and assign it a unique tag.
 | |
|         let sharingConfiguration = VZVirtioFileSystemDeviceConfiguration(tag: tag)
 | |
|         sharingConfiguration.share = share
 | |
| 
 | |
|         return sharingConfiguration
 | |
|     }
 | |
| }
 | |
| 
 |