Pass Softnet policy control FD through Tart (#1287)

* Pass Softnet policy control FD through Tart

* Update Softnet control test for policy set
This commit is contained in:
Fedor Kororkov
2026-07-21 22:23:08 +01:00
committed by GitHub
parent b9ed1a98f0
commit cbc160a592
3 changed files with 245 additions and 4 deletions
+10 -3
View File
@@ -224,6 +224,13 @@ struct Run: AsyncParsableCommand {
""", valueName: "comma-separated CIDRs"))
var netSoftnetBlock: String?
@Option(help: ArgumentHelp("Connected Unix stream socket file descriptor to use for the Softnet control channel (e.g. --net-softnet-control-fd=3)", discussion: """
This option enables the Softnet control channel on an inherited Unix stream socket. It can be used to dynamically replace Softnet allow and block lists while the VM is running.
The file descriptor must be greater than 2. Implies --net-softnet.
""", valueName: "file descriptor"))
var netSoftnetControlFd: Int32?
@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:
@@ -313,7 +320,7 @@ struct Run: AsyncParsableCommand {
}
// Automatically enable --net-softnet when any of its related options are specified
if netSoftnetAllow != nil || netSoftnetBlock != nil || netSoftnetExpose != nil {
if netSoftnetAllow != nil || netSoftnetBlock != nil || netSoftnetExpose != nil || netSoftnetControlFd != nil {
netSoftnet = true
}
@@ -681,13 +688,13 @@ struct Run: AsyncParsableCommand {
if netSoftnet {
let config = try VMConfig.init(fromURL: vmDir.configURL)
return try Softnet(vmMACAddress: config.macAddress.string, extraArguments: softnetExtraArguments)
return try Softnet(vmMACAddress: config.macAddress.string, extraArguments: softnetExtraArguments, controlFD: netSoftnetControlFd)
}
if netHost {
let config = try VMConfig.init(fromURL: vmDir.configURL)
return try Softnet(vmMACAddress: config.macAddress.string, extraArguments: ["--vm-net-type", "host"] + softnetExtraArguments)
return try Softnet(vmMACAddress: config.macAddress.string, extraArguments: ["--vm-net-type", "host"] + softnetExtraArguments, controlFD: netSoftnetControlFd)
}
if netBridged.count > 0 {
+52 -1
View File
@@ -16,7 +16,18 @@ class Softnet: Network {
let vmFD: Int32
init(vmMACAddress: String, extraArguments: [String] = []) throws {
init(vmMACAddress: String, extraArguments: [String] = [], controlFD: Int32? = nil) throws {
var controlFileHandle: FileHandle?
if let controlFD = controlFD {
guard controlFD > STDERR_FILENO else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be greater than 2")
}
controlFileHandle = FileHandle(fileDescriptor: controlFD, closeOnDealloc: true)
try Self.validateControlFD(controlFD)
}
let fds = UnsafeMutablePointer<Int32>.allocate(capacity: MemoryLayout<Int>.stride * 2)
let ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)
@@ -33,6 +44,44 @@ class Softnet: Network {
process.executableURL = try Self.softnetExecutableURL()
process.arguments = ["--vm-fd", String(STDIN_FILENO), "--vm-mac-address", vmMACAddress] + extraArguments
process.standardInput = FileHandle(fileDescriptor: softnetFD, closeOnDealloc: false)
if let controlFileHandle = controlFileHandle {
process.arguments! += ["--control-fd", String(STDOUT_FILENO)]
process.standardOutput = controlFileHandle
}
}
static func validateControlFD(_ fd: Int32) throws {
guard fd > STDERR_FILENO else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be greater than 2")
}
var socketType: Int32 = 0
var socketTypeLength = socklen_t(MemoryLayout<Int32>.size)
guard getsockopt(fd, SOL_SOCKET, SO_TYPE, &socketType, &socketTypeLength) == 0 else {
let details = Errno(rawValue: CInt(errno))
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor is not a socket: \(details)")
}
guard socketType == SOCK_STREAM else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be a Unix stream socket")
}
var peerAddress = sockaddr_storage()
var peerAddressLength = socklen_t(MemoryLayout<sockaddr_storage>.size)
let result = withUnsafeMutablePointer(to: &peerAddress) { pointer in
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) {
getpeername(fd, $0, &peerAddressLength)
}
}
guard result == 0 else {
let details = Errno(rawValue: CInt(errno))
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor is not connected: \(details)")
}
guard peerAddress.ss_family == sa_family_t(AF_UNIX) else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be a Unix stream socket")
}
}
static func softnetExecutableURL() throws -> URL {
@@ -46,6 +95,8 @@ class Softnet: Network {
}
func run(_ sema: AsyncSemaphore) throws {
defer { try? (process.standardOutput as? FileHandle)?.close() }
try process.run()
monitorTask = Task {