diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 2af1783b..8ca8f010 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -202,15 +202,28 @@ struct Run: AsyncParsableCommand { """)) var netSoftnet: Bool = false - @Option(help: ArgumentHelp("Comma-separated list of CIDRs to allow the traffic to when using Softnet isolation\n(e.g. --net-softnet-allow=192.168.0.0/24)", discussion: """ + @Option(help: ArgumentHelp("Comma-separated list of CIDRs to allow the traffic to when using Softnet isolation (e.g. --net-softnet-allow=192.168.0.0/24)", discussion: """ This option allows you bypass the private IPv4 address space restrictions imposed by --net-softnet. - For example, you can allow the VM to communicate with the local network with e.g. --net-softnet-allow=10.0.0.0/16 or to completely disable the destination based restrictions with --net-softnet-allow=0.0.0.0/0. + For example, you can allow the VM to communicate with the local network with e.g. --net-softnet-allow=10.0.0.0/16 or with --net-softnet-allow=0.0.0.0/0 to completely disable the destination based restrictions, including VMs bridge isolation. + + When used with --net-softnet-block, the longest prefix match always wins. In case the same prefix is both allowed and blocked, blocking takes precedence. Implies --net-softnet. """, valueName: "comma-separated CIDRs")) var netSoftnetAllow: String? + @Option(help: ArgumentHelp("Comma-separated list of CIDRs to block the traffic to when using Softnet isolation (e.g. --net-softnet-block=66.66.0.0/16)", discussion: """ + This option allows you to tighten the IPv4 address space restrictions imposed by --net-softnet even further. + + For example --net-softnet-block=0.0.0.0/0 may be used to establish a default deny policy that is further relaxed with --net-softnet-allow. + + When used with --net-softnet-allow, the longest prefix match always wins. In case the same prefix is both allowed and blocked, blocking takes precedence. + + Implies --net-softnet. + """, valueName: "comma-separated CIDRs")) + var netSoftnetBlock: String? + @Option(help: ArgumentHelp("Comma-separated list of TCP ports to expose (e.g. --net-softnet-expose 2222:22,8080:80)", discussion: """ Options are comma-separated and are as follows: @@ -278,7 +291,7 @@ struct Run: AsyncParsableCommand { } // Automatically enable --net-softnet when any of its related options are specified - if netSoftnetAllow != nil || netSoftnetExpose != nil { + if netSoftnetAllow != nil || netSoftnetBlock != nil || netSoftnetExpose != nil { netSoftnet = true } @@ -610,6 +623,10 @@ struct Run: AsyncParsableCommand { softnetExtraArguments += ["--allow", netSoftnetAllow] } + if let netSoftnetBlock = netSoftnetBlock { + softnetExtraArguments += ["--block", netSoftnetBlock] + } + if let netSoftnetExpose = netSoftnetExpose { softnetExtraArguments += ["--expose", netSoftnetExpose] } diff --git a/Sources/tart/Commands/Set.swift b/Sources/tart/Commands/Set.swift index fbb55d68..384fda8c 100644 --- a/Sources/tart/Commands/Set.swift +++ b/Sources/tart/Commands/Set.swift @@ -14,7 +14,7 @@ struct Set: AsyncParsableCommand { @Option(help: "VM memory size in megabytes") var memory: UInt64? - @Option(help: "VM display resolution in a format of x. For example, 1200x800") + @Option(help: "VM display resolution in a format of WIDTHxHEIGHT[pt|px]. For example, 1200x800, 1200x800pt or 1920x1080px. Units are treated as hints and default to \"pt\" (points) for macOS VMs and \"px\" (pixels) for Linux VMs when not specified.") var display: VMDisplayConfig? @Flag(inversion: .prefixedNo, help: ArgumentHelp("Whether to automatically reconfigure the VM's display to fit the window")) @@ -56,6 +56,7 @@ struct Set: AsyncParsableCommand { if (display.height > 0) { vmConfig.display.height = display.height } + vmConfig.display.unit = display.unit } vmConfig.displayRefit = displayRefit @@ -88,12 +89,24 @@ struct Set: AsyncParsableCommand { extension VMDisplayConfig: ExpressibleByArgument { public init(argument: String) { + var argument = argument + var unit: Unit? = nil + + if argument.hasSuffix(Unit.pixel.rawValue) { + argument = String(argument.dropLast(Unit.pixel.rawValue.count)) + unit = Unit.pixel + } else if argument.hasSuffix(Unit.point.rawValue) { + argument = String(argument.dropLast(Unit.point.rawValue.count)) + unit = Unit.point + } + let parts = argument.components(separatedBy: "x").map { Int($0) ?? 0 } self = VMDisplayConfig( width: parts[safe: 0] ?? 0, - height: parts[safe: 1] ?? 0 + height: parts[safe: 1] ?? 0, + unit: unit, ) } } diff --git a/Sources/tart/Platform/Darwin.swift b/Sources/tart/Platform/Darwin.swift index 2aeb2a77..dcdca09a 100644 --- a/Sources/tart/Platform/Darwin.swift +++ b/Sources/tart/Platform/Darwin.swift @@ -82,7 +82,7 @@ struct UnsupportedHostOSError: Error, CustomStringConvertible { func graphicsDevice(vmConfig: VMConfig) -> VZGraphicsDeviceConfiguration { let result = VZMacGraphicsDeviceConfiguration() - if let hostMainScreen = NSScreen.main { + if (vmConfig.display.unit ?? .point) == .point, let hostMainScreen = NSScreen.main { let vmScreenSize = NSSize(width: vmConfig.display.width, height: vmConfig.display.height) result.displays = [ VZMacGraphicsDisplayConfiguration(for: hostMainScreen, sizeInPoints: vmScreenSize) diff --git a/Sources/tart/VMConfig.swift b/Sources/tart/VMConfig.swift index 198cb24f..c6e9ba9a 100644 --- a/Sources/tart/VMConfig.swift +++ b/Sources/tart/VMConfig.swift @@ -32,14 +32,24 @@ enum CodingKeys: String, CodingKey { case hardwareModel } -struct VMDisplayConfig: Codable { +struct VMDisplayConfig: Codable, Equatable { + enum Unit: String, Codable { + case point = "pt" + case pixel = "px" + } + var width: Int = 1024 var height: Int = 768 + var unit: Unit? } extension VMDisplayConfig: CustomStringConvertible { var description: String { - "\(width)x\(height)" + if let unit { + "\(width)x\(height)\(unit.rawValue)" + } else { + "\(width)x\(height)" + } } } diff --git a/Tests/TartTests/VMConfigTests.swift b/Tests/TartTests/VMConfigTests.swift new file mode 100644 index 00000000..34fb9012 --- /dev/null +++ b/Tests/TartTests/VMConfigTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import tart + +final class VMConfigTests: XCTestCase { + func testVMDisplayConfig() throws { + // Defaults units (points) + var vmDisplayConfig = VMDisplayConfig.init(argument: "1234x5678") + XCTAssertEqual(VMDisplayConfig(width: 1234, height: 5678, unit: nil), vmDisplayConfig) + + // Explicit units (points) + vmDisplayConfig = VMDisplayConfig.init(argument: "1234x5678pt") + XCTAssertEqual(VMDisplayConfig(width: 1234, height: 5678, unit: .point), vmDisplayConfig) + + // Explicit units (pixels) + vmDisplayConfig = VMDisplayConfig.init(argument: "1234x5678px") + XCTAssertEqual(VMDisplayConfig(width: 1234, height: 5678, unit: .pixel), vmDisplayConfig) + } +}