mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-17 11:25:33 +02:00
Treat an empty successful ARP cache as no address match (#1363)
Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com>
This commit is contained in:
@@ -41,9 +41,9 @@ struct ARPCacheInternalError: Error, CustomStringConvertible {
|
||||
struct ARPCache {
|
||||
let arpCommandOutput: Data
|
||||
|
||||
init() throws {
|
||||
init(arpCommandURL: URL = URL(fileURLWithPath: "/usr/sbin/arp")) throws {
|
||||
let process = Process.init()
|
||||
process.executableURL = URL.init(fileURLWithPath: "/usr/sbin/arp")
|
||||
process.executableURL = arpCommandURL
|
||||
process.arguments = ["-an"]
|
||||
|
||||
let pipe = Pipe()
|
||||
@@ -53,9 +53,7 @@ struct ARPCache {
|
||||
|
||||
try process.run()
|
||||
|
||||
guard let arpCommandOutput = try pipe.fileHandleForReading.readToEnd() else {
|
||||
throw ARPCommandYieldedInvalidOutputError(explanation: "empty output")
|
||||
}
|
||||
let arpCommandOutput = try pipe.fileHandleForReading.readToEnd() ?? Data()
|
||||
|
||||
process.waitUntilExit()
|
||||
|
||||
@@ -69,9 +67,12 @@ struct ARPCache {
|
||||
}
|
||||
|
||||
func ResolveMACAddress(macAddress: MACAddress) throws -> IPv4Address? {
|
||||
let lines = String(decoding: arpCommandOutput, as: UTF8.self)
|
||||
let output = String(decoding: arpCommandOutput, as: UTF8.self)
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.components(separatedBy: "\n")
|
||||
if output.isEmpty {
|
||||
return nil
|
||||
}
|
||||
let lines = output.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>.*) .*$"#)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import XCTest
|
||||
import Foundation
|
||||
import Network
|
||||
@testable import tart
|
||||
|
||||
final class ARPCacheTests: XCTestCase {
|
||||
private let mac = MACAddress(fromString: "00:11:22:33:44:55")!
|
||||
|
||||
private func cache(_ output: String, exitCode: Int = 0) throws -> ARPCache {
|
||||
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
defer { try? FileManager.default.removeItem(at: directory) }
|
||||
let command = directory.appendingPathComponent("arp-fixture")
|
||||
let quoted = output.replacingOccurrences(of: "'", with: "'\"'\"'")
|
||||
try "#!/bin/sh\nprintf '%s' '\(quoted)'\nexit \(exitCode)\n".write(to: command, atomically: true, encoding: .utf8)
|
||||
try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: command.path)
|
||||
return try ARPCache(arpCommandURL: command)
|
||||
}
|
||||
|
||||
func testEmptySuccessfulOutputHasNoAddress() throws {
|
||||
XCTAssertNil(try cache("").ResolveMACAddress(macAddress: mac))
|
||||
}
|
||||
|
||||
func testWhitespaceSuccessfulOutputHasNoAddress() throws {
|
||||
for output in ["\n", " \t\r\n", "\n\n"] {
|
||||
XCTAssertNil(try cache(output).ResolveMACAddress(macAddress: mac))
|
||||
}
|
||||
}
|
||||
|
||||
func testEmptyOutputPreservesCommandFailure() throws {
|
||||
XCTAssertThrowsError(try cache("", exitCode: 7)) { error in
|
||||
XCTAssertEqual((error as? ARPCommandFailedError)?.terminationStatus, 7)
|
||||
}
|
||||
}
|
||||
|
||||
func testNonemptyOutputPreservesCommandFailure() throws {
|
||||
XCTAssertThrowsError(try cache("synthetic failure", exitCode: 9)) { error in
|
||||
XCTAssertEqual((error as? ARPCommandFailedError)?.terminationStatus, 9)
|
||||
}
|
||||
}
|
||||
|
||||
func testValidEntryResolvesAddress() throws {
|
||||
let value = try cache("? (192.0.2.8) at 0:11:22:33:44:55 on en0 ifscope [ethernet]\n")
|
||||
XCTAssertEqual(try value.ResolveMACAddress(macAddress: mac), IPv4Address("192.0.2.8"))
|
||||
}
|
||||
|
||||
func testIncompleteAndOtherEntriesDoNotMatch() throws {
|
||||
let value = try cache("""
|
||||
? (192.0.2.9) at (incomplete) on en0 ifscope [ethernet]
|
||||
? (192.0.2.10) at aa:bb:cc:dd:ee:ff on en0 ifscope [ethernet]
|
||||
""")
|
||||
XCTAssertNil(try value.ResolveMACAddress(macAddress: mac))
|
||||
}
|
||||
|
||||
func testMalformedNonemptyOutputStillFails() throws {
|
||||
XCTAssertThrowsError(try cache("synthetic invalid entry").ResolveMACAddress(macAddress: mac)) {
|
||||
XCTAssertTrue($0 is ARPCommandYieldedInvalidOutputError)
|
||||
}
|
||||
}
|
||||
|
||||
func testInvalidAddressesStillFail() throws {
|
||||
for line in [
|
||||
"? (not-an-ip) at 0:11:22:33:44:55 on en0 ifscope [ethernet]",
|
||||
"? (192.0.2.8) at not-a-mac on en0 ifscope [ethernet]",
|
||||
] {
|
||||
XCTAssertThrowsError(try cache(line).ResolveMACAddress(macAddress: mac)) {
|
||||
XCTAssertTrue($0 is ARPCommandYieldedInvalidOutputError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user