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:
Sylvester Kaczmarek
2026-10-15 00:10:41 -07:00
committed by GitHub
parent 12501370cc
commit e2042dedcd
2 changed files with 79 additions and 7 deletions
@@ -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>.*) .*$"#)
+71
View File
@@ -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)
}
}
}
}