mirror of https://github.com/cirruslabs/tart.git
184 lines
6.5 KiB
Swift
184 lines
6.5 KiB
Swift
import XCTest
|
|
@testable import tart
|
|
|
|
import Semaphore
|
|
|
|
final class SoftnetControlFDTests: XCTestCase {
|
|
func testConnectedUnixStreamSocketIsAccepted() throws {
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(socketpair(AF_UNIX, SOCK_STREAM, 0, &fds), 0)
|
|
defer {
|
|
close(fds[0])
|
|
close(fds[1])
|
|
}
|
|
|
|
XCTAssertNoThrow(try Softnet.validateControlFD(fds[0]))
|
|
}
|
|
|
|
func testUnixDatagramSocketIsRejected() throws {
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds), 0)
|
|
defer {
|
|
close(fds[0])
|
|
close(fds[1])
|
|
}
|
|
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(fds[0]))
|
|
}
|
|
|
|
func testUnconnectedUnixStreamSocketIsRejected() throws {
|
|
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
|
|
XCTAssertGreaterThan(fd, STDERR_FILENO)
|
|
defer { close(fd) }
|
|
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(fd))
|
|
}
|
|
|
|
func testPipeIsRejected() throws {
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(pipe(&fds), 0)
|
|
defer {
|
|
close(fds[0])
|
|
close(fds[1])
|
|
}
|
|
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(fds[0]))
|
|
}
|
|
|
|
func testStandardDescriptorsAreRejected() throws {
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(STDIN_FILENO))
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(STDOUT_FILENO))
|
|
XCTAssertThrowsError(try Softnet.validateControlFD(STDERR_FILENO))
|
|
}
|
|
|
|
func testStandardDescriptorsRemainOpenWhenInitializationFails() throws {
|
|
for fd in [STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] {
|
|
let flags = fcntl(fd, F_GETFD)
|
|
XCTAssertNotEqual(flags, -1)
|
|
|
|
XCTAssertThrowsError(try Softnet(vmMACAddress: "02:00:00:00:00:01", controlFD: fd))
|
|
XCTAssertEqual(fcntl(fd, F_GETFD), flags)
|
|
}
|
|
}
|
|
|
|
func testControlChannelIsPassedToSoftnetAndVMFDRemainsDatagram() async throws {
|
|
let temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
|
|
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: false)
|
|
defer { try? FileManager.default.removeItem(at: temporaryDirectory) }
|
|
|
|
let executable = temporaryDirectory.appendingPathComponent("softnet")
|
|
let script = """
|
|
#!/usr/bin/env python3
|
|
import socket
|
|
import sys
|
|
|
|
assert sys.argv[1:] == ["--vm-fd", "0", "--vm-mac-address", "02:00:00:00:00:01", "--control-fd", "1"]
|
|
vm = socket.socket(fileno=0)
|
|
control = socket.socket(fileno=1)
|
|
assert vm.family == socket.AF_UNIX and vm.type == socket.SOCK_DGRAM
|
|
assert control.family == socket.AF_UNIX and control.type == socket.SOCK_STREAM
|
|
assert control.recv(4096) == b"softnet.policy.set\\n"
|
|
control.sendall(b"ok\\n")
|
|
"""
|
|
try script.write(to: executable, atomically: true, encoding: .utf8)
|
|
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: executable.path)
|
|
|
|
let previousPath = ProcessInfo.processInfo.environment["PATH"] ?? ""
|
|
setenv("PATH", "\(temporaryDirectory.path):\(previousPath)", 1)
|
|
defer { setenv("PATH", previousPath, 1) }
|
|
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(socketpair(AF_UNIX, SOCK_STREAM, 0, &fds), 0)
|
|
defer { close(fds[1]) }
|
|
|
|
var timeout = timeval(tv_sec: 5, tv_usec: 0)
|
|
XCTAssertEqual(setsockopt(fds[1], SOL_SOCKET, SO_RCVTIMEO, &timeout, socklen_t(MemoryLayout<timeval>.size)), 0)
|
|
|
|
let semaphore = AsyncSemaphore(value: 0)
|
|
let softnet = try Softnet(vmMACAddress: "02:00:00:00:00:01", controlFD: fds[0])
|
|
try softnet.run(semaphore)
|
|
|
|
XCTAssertEqual(fcntl(fds[0], F_GETFD), -1)
|
|
XCTAssertEqual(errno, EBADF)
|
|
|
|
let request = Array("softnet.policy.set\n".utf8)
|
|
XCTAssertEqual(request.withUnsafeBytes { send(fds[1], $0.baseAddress, $0.count, 0) }, request.count)
|
|
|
|
var response = [UInt8](repeating: 0, count: 128)
|
|
let received = recv(fds[1], &response, response.count, 0)
|
|
XCTAssertGreaterThan(received, 0)
|
|
XCTAssertEqual(String(decoding: response.prefix(Int(max(received, 0))), as: UTF8.self), "ok\n")
|
|
|
|
await semaphore.wait()
|
|
}
|
|
|
|
func testControlFDIsClosedWhenSoftnetInitializationFails() throws {
|
|
let previousPath = ProcessInfo.processInfo.environment["PATH"] ?? ""
|
|
setenv("PATH", "/this/path/does/not/exist", 1)
|
|
defer { setenv("PATH", previousPath, 1) }
|
|
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(socketpair(AF_UNIX, SOCK_STREAM, 0, &fds), 0)
|
|
defer { close(fds[1]) }
|
|
|
|
XCTAssertThrowsError(try Softnet(vmMACAddress: "02:00:00:00:00:01", controlFD: fds[0]))
|
|
XCTAssertEqual(fcntl(fds[0], F_GETFD), -1)
|
|
XCTAssertEqual(errno, EBADF)
|
|
}
|
|
|
|
func testControlFDIsClosedWhenSoftnetValidationFails() throws {
|
|
var fds: [Int32] = [-1, -1]
|
|
XCTAssertEqual(socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds), 0)
|
|
defer { close(fds[1]) }
|
|
|
|
XCTAssertThrowsError(try Softnet(vmMACAddress: "02:00:00:00:00:01", controlFD: fds[0]))
|
|
XCTAssertEqual(fcntl(fds[0], F_GETFD), -1)
|
|
XCTAssertEqual(errno, EBADF)
|
|
}
|
|
|
|
func testControlFDImpliesSoftnet() throws {
|
|
let temporaryHome = try createTemporaryTartHome()
|
|
defer { try? FileManager.default.removeItem(at: temporaryHome) }
|
|
let previousHome = ProcessInfo.processInfo.environment["TART_HOME"]
|
|
setenv("TART_HOME", temporaryHome.path, 1)
|
|
defer { restoreEnvironment("TART_HOME", value: previousHome) }
|
|
|
|
let command = try Run.parse(["vm", "--net-softnet-control-fd", "3"])
|
|
|
|
XCTAssertTrue(command.netSoftnet)
|
|
XCTAssertEqual(command.netSoftnetControlFd, 3)
|
|
}
|
|
|
|
func testControlFDIsRejectedWithHostNetworking() throws {
|
|
let temporaryHome = try createTemporaryTartHome()
|
|
defer { try? FileManager.default.removeItem(at: temporaryHome) }
|
|
let previousHome = ProcessInfo.processInfo.environment["TART_HOME"]
|
|
setenv("TART_HOME", temporaryHome.path, 1)
|
|
defer { restoreEnvironment("TART_HOME", value: previousHome) }
|
|
|
|
XCTAssertThrowsError(
|
|
try Run.parse(["vm", "--net-host", "--net-softnet-control-fd", "3"])
|
|
)
|
|
}
|
|
|
|
private func createTemporaryTartHome() throws -> URL {
|
|
let temporaryHome = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
|
|
let vm = temporaryHome.appendingPathComponent("vms/vm")
|
|
try FileManager.default.createDirectory(at: vm, withIntermediateDirectories: true)
|
|
|
|
for name in ["config.json", "disk.img", "nvram.bin"] {
|
|
XCTAssertTrue(FileManager.default.createFile(atPath: vm.appendingPathComponent(name).path, contents: nil))
|
|
}
|
|
|
|
return temporaryHome
|
|
}
|
|
|
|
private func restoreEnvironment(_ name: String, value: String?) {
|
|
if let value = value {
|
|
setenv(name, value, 1)
|
|
} else {
|
|
unsetenv(name)
|
|
}
|
|
}
|
|
}
|