这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions Sources/tart/ARP/ARPCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import Foundation
import Network
import Virtualization

struct ARPCommandFailedError: Error, CustomStringConvertible {
var terminationReason: Process.TerminationReason
var terminationStatus: Int32

var description: String {
var reason: String

switch terminationReason {
case .exit:
reason = "exit code \(terminationStatus)"
case .uncaughtSignal:
reason = "uncaught signal"
default:
reason = "unknown reason"
}

return "arp command failed: \(reason)"
}
}

struct ARPCommandYieldedInvalidOutputError: Error, CustomStringConvertible {
var explanation: String

var description: String {
"arp command yielded invalid output: \(explanation)"
}
}

struct ARPCacheInternalError: Error, CustomStringConvertible {
var explanation: String

var description: String {
"ARPCache internal error: \(explanation)"
}
}

struct ARPCache {
static func ResolveMACAddress(macAddress: MACAddress, bridgeOnly: Bool = true) throws -> IPv4Address? {
let process = Process.init()
process.executableURL = URL.init(fileURLWithPath: "/usr/sbin/arp")
process.arguments = ["-an"]

let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
process.standardInput = FileHandle.nullDevice

try process.run()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a catch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process.waitUntilExit()

if !(process.terminationReason == .exit && process.terminationStatus == 0) {
throw ARPCommandFailedError(
terminationReason: process.terminationReason,
terminationStatus: process.terminationStatus)
}

guard let rawLines = try pipe.fileHandleForReading.readToEnd() else {
throw ARPCommandYieldedInvalidOutputError(explanation: "empty output")
}
let lines = String(decoding: rawLines, as: UTF8.self)
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n")

// Based on https://opensource.apple.com/source/network_cmds/network_cmds-606.40.2/arp.tproj/arp.c.auto.html
let regex = try NSRegularExpression(pattern: #"^.* \((?<ip>.*)\) at (?<mac>.*) on (?<interface>.*) .*$"#)

for line in lines {
let nsLineRange = NSRange(line.startIndex..<line.endIndex, in: line)

guard let match = regex.firstMatch(in: line, range: nsLineRange) else {
throw ARPCommandYieldedInvalidOutputError(explanation: "unparseable entry \"\(line)\"")
}

let rawIP = try match.getCaptureGroup(name: "ip", for: line)
guard let ip = IPv4Address(rawIP) else {
throw ARPCommandYieldedInvalidOutputError(explanation: "failed to parse IPv4 address \(rawIP)")
}

let rawMAC = try match.getCaptureGroup(name: "mac", for: line)
guard let mac = MACAddress(fromString: rawMAC) else {
throw ARPCommandYieldedInvalidOutputError(explanation: "failed to parse MAC address \(rawMAC)")
}

let interface = try match.getCaptureGroup(name: "interface", for: line)
if bridgeOnly && !interface.starts(with: "bridge") {
continue
}

if macAddress == mac {
return ip
}
}

return nil
}
}

extension NSTextCheckingResult {
func getCaptureGroup(name: String, for string: String) throws -> String {
let nsRange = self.range(withName: name)

if nsRange.location == NSNotFound {
throw ARPCacheInternalError(explanation: "attempted to retrieve non-existent named capture group \(name)")
}

guard let range = Range.init(nsRange, in: string) else {
throw ARPCacheInternalError(explanation: "failed to convert NSRange to Range")
}

return String(string[range])
}
}
21 changes: 21 additions & 0 deletions Sources/tart/ARP/MACAddress.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

struct MACAddress: Equatable, CustomStringConvertible {
var mac: [UInt8] = Array(repeating: 0, count: 6)

init?(fromString: String) {
let components = fromString.components(separatedBy: ":")

if components.count != 6 {
return nil
}

for (index, component) in components.enumerated() {
mac[index] = UInt8(component, radix: 16)!
}
}

var description: String {
return String(format: "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}
}
36 changes: 36 additions & 0 deletions Sources/tart/Commands/IP.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ArgumentParser
import Foundation
import SystemConfiguration

struct IP: ParsableCommand {
static var configuration = CommandConfiguration(abstract: "Get VM's IP address")

@Argument(help: "VM name")
var name: String

func run() throws {
Task {
do {
let vmDir = try VMStorage().read(name)
let vmConfig = try VMConfig.init(fromURL: vmDir.configURL)
let vmMacAddress = MACAddress(fromString: vmConfig.macAddress.string)!

guard let ip = try ARPCache.ResolveMACAddress(macAddress: vmMacAddress) else {
print("no IP address found, is your VM running?")

Foundation.exit(1)
}

print(ip)

Foundation.exit(0)
} catch {
print(error)

Foundation.exit(1)
}
}

dispatchMain()
}
}
2 changes: 1 addition & 1 deletion Sources/tart/Commands/Root.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import ArgumentParser
struct Root: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "tart",
subcommands: [Create.self, Run.self, List.self, Delete.self])
subcommands: [Create.self, Run.self, List.self, IP.self, Delete.self])
}
10 changes: 7 additions & 3 deletions Sources/tart/VM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
auxStorage: auxStorage,
hardwareModel: vmConfig.hardwareModel,
cpuCount: vmConfig.cpuCount,
memorySize: vmConfig.memorySize
memorySize: vmConfig.memorySize,
macAddress: vmConfig.macAddress
)

self.virtualMachine = VZVirtualMachine(configuration: configuration)
Expand Down Expand Up @@ -80,7 +81,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
auxStorage: auxStorage,
hardwareModel: requirements.hardwareModel,
cpuCount: self.vmConfig.cpuCount,
memorySize: self.vmConfig.memorySize
memorySize: self.vmConfig.memorySize,
macAddress: self.vmConfig.macAddress
)
self.virtualMachine = VZVirtualMachine(configuration: configuration)

Expand Down Expand Up @@ -116,7 +118,8 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
auxStorage: VZMacAuxiliaryStorage,
hardwareModel: VZMacHardwareModel,
cpuCount: Int,
memorySize: UInt64
memorySize: UInt64,
macAddress: VZMACAddress
) throws -> VZVirtualMachineConfiguration {
let configuration = VZVirtualMachineConfiguration()

Expand Down Expand Up @@ -153,6 +156,7 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
// Networking
let vio = VZVirtioNetworkDeviceConfiguration()
vio.attachment = VZNATNetworkDeviceAttachment()
vio.macAddress = macAddress
configuration.networkDevices = [vio]

// Storage
Expand Down
21 changes: 20 additions & 1 deletion Sources/tart/VMConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum CodingKeys: String, CodingKey {
case hardwareModel
case cpuCount
case memorySize
case macAddress
}

struct VMConfig: Encodable, Decodable {
Expand All @@ -14,12 +15,20 @@ struct VMConfig: Encodable, Decodable {
var hardwareModel: VZMacHardwareModel
var cpuCount: Int
var memorySize: UInt64
var macAddress: VZMACAddress

init(ecid: VZMacMachineIdentifier = VZMacMachineIdentifier(), hardwareModel: VZMacHardwareModel, cpuCount: Int, memorySize: UInt64) {
init(
ecid: VZMacMachineIdentifier = VZMacMachineIdentifier(),
hardwareModel: VZMacHardwareModel,
cpuCount: Int,
memorySize: UInt64,
macAddress: VZMACAddress = VZMACAddress.randomLocallyAdministered()
) {
self.ecid = ecid
self.hardwareModel = hardwareModel
self.cpuCount = cpuCount
self.memorySize = memorySize
self.macAddress = macAddress
}

init(fromURL: URL) throws {
Expand Down Expand Up @@ -63,6 +72,15 @@ struct VMConfig: Encodable, Decodable {
self.cpuCount = try container.decode(Int.self, forKey: .cpuCount)

self.memorySize = try container.decode(UInt64.self, forKey: .memorySize)

let encodedMacAddress = try container.decode(String.self, forKey: .macAddress)
guard let macAddress = VZMACAddress.init(string: encodedMacAddress) else {
throw DecodingError.dataCorruptedError(
forKey: .hardwareModel,
in: container,
debugDescription: "failed to initialize VZMacAddress using the provided value")
}
self.macAddress = macAddress
}

func encode(to encoder: Encoder) throws {
Expand All @@ -73,5 +91,6 @@ struct VMConfig: Encodable, Decodable {
try container.encode(self.hardwareModel.dataRepresentation.base64EncodedString(), forKey: .hardwareModel)
try container.encode(self.cpuCount, forKey: .cpuCount)
try container.encode(self.memorySize, forKey: .memorySize)
try container.encode(self.macAddress.string, forKey: .macAddress)
}
}
20 changes: 20 additions & 0 deletions tart.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
440478FD27B1352C0028EFB8 /* Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440478FC27B1352C0028EFB8 /* Create.swift */; };
440478FF27B13D590028EFB8 /* Run.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440478FE27B13D590028EFB8 /* Run.swift */; };
4473E1E527A94E28000850C3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4473E1E427A94E28000850C3 /* main.swift */; };
4484BA6B27BF1F270043A359 /* IP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4484BA6A27BF1F270043A359 /* IP.swift */; };
4484BA7027BF1F6D0043A359 /* ARPCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4484BA6E27BF1F6D0043A359 /* ARPCache.swift */; };
4484BA7127BF1F6D0043A359 /* MACAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4484BA6F27BF1F6D0043A359 /* MACAddress.swift */; };
44FDBB3427B4177C005A201B /* VMStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB3327B4177C005A201B /* VMStorage.swift */; };
44FDBB4227B43E6D005A201B /* VM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4127B43E6D005A201B /* VM.swift */; };
44FDBB4427B4445E005A201B /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FDBB4327B4445E005A201B /* Root.swift */; };
Expand All @@ -37,6 +40,9 @@
440478FE27B13D590028EFB8 /* Run.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Run.swift; sourceTree = "<group>"; };
4473E1E127A94E27000850C3 /* tart */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tart; sourceTree = BUILT_PRODUCTS_DIR; };
4473E1E427A94E28000850C3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
4484BA6A27BF1F270043A359 /* IP.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IP.swift; sourceTree = "<group>"; };
4484BA6E27BF1F6D0043A359 /* ARPCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPCache.swift; sourceTree = "<group>"; };
4484BA6F27BF1F6D0043A359 /* MACAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MACAddress.swift; sourceTree = "<group>"; };
44FDBB3327B4177C005A201B /* VMStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMStorage.swift; sourceTree = "<group>"; };
44FDBB3927B43CCF005A201B /* tart-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "tart-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
44FDBB4127B43E6D005A201B /* VM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VM.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -91,9 +97,19 @@
path = Sources;
sourceTree = "<group>";
};
4484BA6D27BF1F6D0043A359 /* ARP */ = {
isa = PBXGroup;
children = (
4484BA6E27BF1F6D0043A359 /* ARPCache.swift */,
4484BA6F27BF1F6D0043A359 /* MACAddress.swift */,
);
path = ARP;
sourceTree = "<group>";
};
44FDBB4027B43DCB005A201B /* Commands */ = {
isa = PBXGroup;
children = (
4484BA6A27BF1F270043A359 /* IP.swift */,
440478FC27B1352C0028EFB8 /* Create.swift */,
440478FE27B13D590028EFB8 /* Run.swift */,
44FDBB4327B4445E005A201B /* Root.swift */,
Expand All @@ -106,6 +122,7 @@
44FDBB4F27B6A4B6005A201B /* tart */ = {
isa = PBXGroup;
children = (
4484BA6D27BF1F6D0043A359 /* ARP */,
44FDBB4027B43DCB005A201B /* Commands */,
4473E1E427A94E28000850C3 /* main.swift */,
44FDBB3327B4177C005A201B /* VMStorage.swift */,
Expand Down Expand Up @@ -214,10 +231,13 @@
440478FF27B13D590028EFB8 /* Run.swift in Sources */,
4473E1E527A94E28000850C3 /* main.swift in Sources */,
44FDBB4827B45EA1005A201B /* Delete.swift in Sources */,
4484BA6B27BF1F270043A359 /* IP.swift in Sources */,
4484BA7027BF1F6D0043A359 /* ARPCache.swift in Sources */,
44FDBB3427B4177C005A201B /* VMStorage.swift in Sources */,
44FDBB4627B44B35005A201B /* VMConfig.swift in Sources */,
44FDBB4227B43E6D005A201B /* VM.swift in Sources */,
44FDBB4C27B69515005A201B /* VMDirectory.swift in Sources */,
4484BA7127BF1F6D0043A359 /* MACAddress.swift in Sources */,
440478FD27B1352C0028EFB8 /* Create.swift in Sources */,
44FDBB4427B4445E005A201B /* Root.swift in Sources */,
44FDBB4A27B45F6F005A201B /* List.swift in Sources */,
Expand Down