Compare commits

..
3 changed files with 65 additions and 12 deletions
+17 -1
View File
@@ -187,6 +187,17 @@ struct Run: AsyncParsableCommand {
""", valueName: "interface name"))
var netBridged: [String] = []
@Option(help: ArgumentHelp("Use a connected datagram Unix domain socket inherited from the parent process for networking (e.g. --net-fd=3)",
discussion: """
The socket carries raw Ethernet frames, one per datagram.
The simplest use-case is composing Tart and Softnet invocations that are orchestrated by another program, such as Orchard.
Another way to use this argument is to connect a VM to any other networking implementation such as gVisor or software-defined networking implementation.
""",
valueName: "file descriptor"))
var netFd: Int32?
@Flag(help: ArgumentHelp("Use software networking provided by Softnet instead of the default shared (NAT) networking",
discussion: """
Softnet provides better network isolation and alleviates DHCP shortage on production systems. Tart invokes Softnet when this option is specified as a sub-process and communicates with it over socketpair(2).
@@ -332,9 +343,10 @@ struct Run: AsyncParsableCommand {
if netBridged.count > 0 { netFlags += 1 }
if netSoftnet { netFlags += 1 }
if netHost { netFlags += 1 }
if netFd != nil { netFlags += 1 }
if netFlags > 1 {
throw ValidationError("--net-bridged, --net-softnet and --net-host are mutually exclusive")
throw ValidationError("--net-bridged, --net-softnet, --net-host and --net-fd are mutually exclusive")
}
if graphics && noGraphics {
@@ -725,6 +737,10 @@ struct Run: AsyncParsableCommand {
return NetworkBridged(interfaces: try netBridged.map { try findBridgedInterface($0) })
}
if let netFd = netFd {
return try NetworkFD(fd: netFd)
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
import Darwin
import Foundation
import Semaphore
import System
import Virtualization
class NetworkFD: Network {
private let attachment: VZFileHandleNetworkDeviceAttachment
init(fd: Int32) throws {
do {
try Softnet.validateControlFD(fd, type: SOCK_DGRAM)
} catch {
throw RuntimeError.Generic("--net-fd \(error)")
}
let flags = fcntl(fd, F_GETFD)
guard flags != -1 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1 else {
throw RuntimeError.Generic("failed to set close-on-exec on --net-fd: \(Errno(rawValue: errno))")
}
let fileHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
attachment = VZFileHandleNetworkDeviceAttachment(fileHandle: fileHandle)
}
func attachments() -> [VZNetworkDeviceAttachment] {
[attachment]
}
func run(_ sema: AsyncSemaphore) throws {
// no-op, only used for Softnet
}
func stop() async throws {
// no-op, only used for Softnet
}
}
+11 -11
View File
@@ -24,7 +24,11 @@ class Softnet: Network {
}
controlFileHandle = FileHandle(fileDescriptor: controlFD, closeOnDealloc: true)
try Self.validateControlFD(controlFD)
do {
try Self.validateControlFD(controlFD)
} catch {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor \(error)")
}
}
let fds = UnsafeMutablePointer<Int32>.allocate(capacity: MemoryLayout<Int>.stride * 2)
@@ -50,20 +54,16 @@ class Softnet: Network {
}
}
static func validateControlFD(_ fd: Int32) throws {
guard fd > STDERR_FILENO else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be greater than 2")
}
static func validateControlFD(_ fd: Int32, type: Int32 = SOCK_STREAM) throws {
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)")
throw RuntimeError.Generic("is not a socket: \(details)")
}
guard socketType == SOCK_STREAM else {
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor must be a Unix stream socket")
guard socketType == type else {
throw RuntimeError.Generic("must be a \(type == SOCK_STREAM ? "stream" : "datagram") socket")
}
var peerAddress = sockaddr_storage()
@@ -75,11 +75,11 @@ class Softnet: Network {
}
guard result == 0 else {
let details = Errno(rawValue: CInt(errno))
throw SoftnetError.InitializationFailed(why: "Softnet control file descriptor is not connected: \(details)")
throw RuntimeError.Generic("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")
throw RuntimeError.Generic("must be a Unix socket")
}
}