b438e97b93
apple/containerization currently runs containers in per-container VMs on macOS hosts via Virtualization.framework. This adds a second VMM backend so the same Swift orchestration layer (LinuxContainer / LinuxPod / Vminitd gRPC contract) runs on Linux hosts via cloud-hypervisor + KVM. **CloudHypervisor Swift package** (`Sources/CloudHypervisor/`) — a thin client for cloud-hypervisor's REST-over-UDS API, layered on AsyncHTTPClient. Endpoints cover VMM / VM lifecycle / hotplug (disk, fs, net, vsock, remove-device). Cross-platform (compiles on macOS for unit tests; consumed at runtime only by the Linux side of Containerization). **CH backend in Containerization** — one cloud-hypervisor subprocess per VM, gated behind `#if os(Linux)`. CHVirtualMachineManager / CHVirtualMachineInstance mirror the VZ shape behind the existing VirtualMachineManager / VirtualMachineInstance protocol. CHProcess and VirtiofsdProcess manage the binaries; CHHotplugProvider handles virtio-blk and virtio-fs runtime hotplug (with one virtiofsd per unique source-hash tag, refcounted across containers). **Linux host networking** — BridgeManager brings up a Linux bridge with an IPv4 subnet and (opt-in via `--enable-nat`) iptables MASQUERADE + scoped FORWARD rules. LinuxBridgedNetwork enslaves a fresh TAP per container to the bridge. State is recorded under `/run/containerization` so `cctl bridge delete` reverses exactly what create did. Bridge teardown verifies the link kind via sysfs to refuse deleting non-bridge interfaces. **cctl run / bridge** — end-to-end Linux container run path (image pull, ext4 rootfs assembly, VM boot, container exec) plus `cctl bridge create|delete` for the host network plumbing. **Build & dist** — `make linux-build` / `make linux-integration` build and exercise the host side inside an apple/container `--virtualization` dev container. `make dist-x86_64` produces a deployment tarball (cctl + cloud-hypervisor + virtiofsd + initfs + kernel) cross-compiled from the aarch64 dev container; pipeline documented in `docs/x86_64-build.md`. Static-musl C deps and the Zig cross compiler are pinned by SHA256. The host orchestrator runs as root. Per-VM runtime state lives under `/run/containerization/ch/<UUID>` with mode 0700; UDS sockets inside are bound with mode 0600. Vminitd's gRPC channel inherits that trust boundary — socket-file perms are the auth. Sandbox flags are upstream-secure by default. Two per-component opt-outs exist for the apple/container dev-container case (where the host seccomp profile SIGSYS-kills CH and virtiofsd): - `CONTAINERIZATION_NO_CH_SECCOMP=1` — `cloud-hypervisor --seccomp false`. - `CONTAINERIZATION_NO_VIRTIOFSD_SANDBOX=1` — `virtiofsd --sandbox none`. Each emits a one-shot `logger.warning` at process start. Legacy alias `CONTAINERIZATION_RELAXED_SANDBOX=1` flips both. cctl spawns both binaries with `setsid` and a minimal env allowlist (PATH / HOME / RUST_LOG / RUST_BACKTRACE) so the parent's secrets don't leak to children. `make linux-integration` runs the cross-platform integration suite against a real cloud-hypervisor VM inside the dev container. Linux runs the cross-platform subset (`process true`/`false`/`echo hi`, virtiofs round-trip, hotplug); the macOS suite is unchanged. Signed-off-by: michael_crosby <michael_crosby@apple.com>
890 lines
34 KiB
Swift
890 lines
34 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerizationExtras
|
|
|
|
struct SocketType {
|
|
static let SOCK_RAW: Int32 = 3
|
|
}
|
|
|
|
struct AddressFamily {
|
|
static let AF_UNSPEC: UInt16 = 0
|
|
static let AF_INET: UInt16 = 2
|
|
static let AF_INET6: UInt16 = 10
|
|
static let AF_NETLINK: UInt16 = 16
|
|
static let AF_PACKET: UInt16 = 17
|
|
}
|
|
|
|
struct ArpHardware {
|
|
static let ARPHRD_ETHER: UInt16 = 1
|
|
}
|
|
|
|
struct NetlinkProtocol {
|
|
static let NETLINK_ROUTE: Int32 = 0
|
|
}
|
|
|
|
struct NetlinkType {
|
|
static let NLMSG_NOOP: UInt16 = 1
|
|
static let NLMSG_ERROR: UInt16 = 2
|
|
static let NLMSG_DONE: UInt16 = 3
|
|
static let NLMSG_OVERRUN: UInt16 = 4
|
|
static let RTM_NEWLINK: UInt16 = 16
|
|
static let RTM_DELLINK: UInt16 = 17
|
|
static let RTM_GETLINK: UInt16 = 18
|
|
static let RTM_NEWADDR: UInt16 = 20
|
|
static let RTM_NEWROUTE: UInt16 = 24
|
|
}
|
|
|
|
struct NetlinkFlags {
|
|
static let NLM_F_REQUEST: UInt16 = 0x01
|
|
static let NLM_F_MULTI: UInt16 = 0x02
|
|
static let NLM_F_ACK: UInt16 = 0x04
|
|
static let NLM_F_ECHO: UInt16 = 0x08
|
|
static let NLM_F_DUMP_INTR: UInt16 = 0x10
|
|
static let NLM_F_DUMP_FILTERED: UInt16 = 0x20
|
|
|
|
// GET request
|
|
static let NLM_F_ROOT: UInt16 = 0x100
|
|
static let NLM_F_MATCH: UInt16 = 0x200
|
|
static let NLM_F_ATOMIC: UInt16 = 0x400
|
|
static let NLM_F_DUMP: UInt16 = NetlinkFlags.NLM_F_ROOT | NetlinkFlags.NLM_F_MATCH
|
|
|
|
// NEW request flags
|
|
static let NLM_F_REPLACE: UInt16 = 0x100
|
|
static let NLM_F_EXCL: UInt16 = 0x200
|
|
static let NLM_F_CREATE: UInt16 = 0x400
|
|
static let NLM_F_APPEND: UInt16 = 0x800
|
|
}
|
|
|
|
struct NetlinkScope {
|
|
static let RT_SCOPE_UNIVERSE: UInt8 = 0
|
|
}
|
|
|
|
struct InterfaceFlags {
|
|
static let IFF_UP: UInt32 = 1 << 0
|
|
static let IFF_LOOPBACK: UInt32 = 1 << 3
|
|
static let IFF_POINTOPOINT: UInt32 = 1 << 4
|
|
static let DEFAULT_CHANGE: UInt32 = 0xffff_ffff
|
|
}
|
|
|
|
struct LinkAttributeType {
|
|
static let IFLA_ADDRESS: UInt16 = 1
|
|
static let IFLA_BROADCAST: UInt16 = 2
|
|
static let IFLA_IFNAME: UInt16 = 3
|
|
static let IFLA_MTU: UInt16 = 4
|
|
static let IFLA_MASTER: UInt16 = 10
|
|
static let IFLA_LINKINFO: UInt16 = 18
|
|
static let IFLA_STATS64: UInt16 = 23
|
|
static let IFLA_EXT_MASK: UInt16 = 29
|
|
}
|
|
|
|
/// Nested attribute types inside `IFLA_LINKINFO`.
|
|
struct LinkInfoAttributeType {
|
|
static let IFLA_INFO_KIND: UInt16 = 1
|
|
}
|
|
|
|
struct LinkAttributeMaskFilter {
|
|
static let RTEXT_FILTER_VF: UInt32 = 1 << 0
|
|
static let RTEXT_FILTER_SKIP_STATS: UInt32 = 1 << 3
|
|
}
|
|
|
|
struct AddressAttributeType {
|
|
// subnet mask
|
|
static let IFA_ADDRESS: UInt16 = 1
|
|
// IPv4 address
|
|
static let IFA_LOCAL: UInt16 = 2
|
|
}
|
|
|
|
struct AddressFlags {
|
|
static let IFA_F_NODAD: UInt8 = 0x02
|
|
static let IFA_F_PERMANENT: UInt8 = 0x80
|
|
}
|
|
|
|
struct RouteTable {
|
|
static let MAIN: UInt8 = 254
|
|
}
|
|
|
|
struct RouteProtocol {
|
|
static let UNSPEC: UInt8 = 0
|
|
static let REDIRECT: UInt8 = 1
|
|
static let KERNEL: UInt8 = 2
|
|
static let BOOT: UInt8 = 3
|
|
static let STATIC: UInt8 = 4
|
|
}
|
|
|
|
struct RouteScope {
|
|
static let UNIVERSE: UInt8 = 0
|
|
static let LINK: UInt8 = 253
|
|
}
|
|
|
|
struct RouteType {
|
|
static let UNSPEC: UInt8 = 0
|
|
static let UNICAST: UInt8 = 1
|
|
}
|
|
|
|
struct RouteAttributeType {
|
|
static let UNSPEC: UInt16 = 0
|
|
static let DST: UInt16 = 1
|
|
static let SRC: UInt16 = 2
|
|
static let IIF: UInt16 = 3
|
|
static let OIF: UInt16 = 4
|
|
static let GATEWAY: UInt16 = 5
|
|
static let PRIORITY: UInt16 = 6
|
|
static let PREFSRC: UInt16 = 7
|
|
}
|
|
|
|
struct SockaddrNetlink: Bindable, Equatable {
|
|
static let size = 12
|
|
|
|
var family: UInt16
|
|
var _pad: UInt16 = 0
|
|
var pid: UInt32
|
|
var groups: UInt32
|
|
|
|
init(family: UInt16 = 0, pid: UInt32 = 0, groups: UInt32 = 0) {
|
|
self.family = family
|
|
self.pid = pid
|
|
self.groups = groups
|
|
}
|
|
|
|
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: family, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "family")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: 0, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "_pad")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: pid, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "pid")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: groups, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "SockaddrNetlink", field: "groups")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "family")
|
|
}
|
|
family = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "_pad")
|
|
}
|
|
_pad = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "pid")
|
|
}
|
|
pid = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "SockaddrNetlink", field: "groups")
|
|
}
|
|
groups = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
struct NetlinkMessageHeader: Bindable, Equatable {
|
|
static let size = 16
|
|
|
|
var len: UInt32
|
|
var type: UInt16
|
|
var flags: UInt16
|
|
var seq: UInt32
|
|
var pid: UInt32
|
|
|
|
init(len: UInt32 = 0, type: UInt16 = 0, flags: UInt16 = 0, seq: UInt32? = nil, pid: UInt32 = 0) {
|
|
self.len = len
|
|
self.type = type
|
|
self.flags = flags
|
|
self.seq = seq ?? UInt32.random(in: 0..<UInt32.max)
|
|
self.pid = pid
|
|
}
|
|
|
|
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: len, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "len")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "type")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: flags, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "flags")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: seq, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "seq")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: pid, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "NetlinkMessageHeader", field: "pid")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "len")
|
|
}
|
|
len = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "type")
|
|
}
|
|
type = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "flags")
|
|
}
|
|
flags = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "seq")
|
|
}
|
|
seq = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "NetlinkMessageHeader", field: "pid")
|
|
}
|
|
pid = value
|
|
|
|
return offset
|
|
}
|
|
|
|
var moreResponses: Bool {
|
|
(self.flags & NetlinkFlags.NLM_F_MULTI) != 0
|
|
&& (self.type != NetlinkType.NLMSG_DONE && self.type != NetlinkType.NLMSG_ERROR
|
|
&& self.type != NetlinkType.NLMSG_OVERRUN)
|
|
}
|
|
}
|
|
|
|
struct InterfaceInfo: Bindable, Equatable {
|
|
static let size = 16
|
|
|
|
var family: UInt8
|
|
var _pad: UInt8 = 0
|
|
var type: UInt16
|
|
var index: Int32
|
|
var flags: UInt32
|
|
var change: UInt32
|
|
|
|
init(
|
|
family: UInt8 = UInt8(AddressFamily.AF_UNSPEC), type: UInt16 = 0, index: Int32 = 0, flags: UInt32 = 0,
|
|
change: UInt32 = 0
|
|
) {
|
|
self.family = family
|
|
self.type = type
|
|
self.index = index
|
|
self.flags = flags
|
|
self.change = change
|
|
}
|
|
|
|
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "family")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: _pad, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "_pad")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "type")
|
|
}
|
|
guard let offset = buffer.copyIn(as: Int32.self, value: index, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "index")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: flags, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "flags")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: change, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "InterfaceInfo", field: "change")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "family")
|
|
}
|
|
family = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "_pad")
|
|
}
|
|
_pad = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "type")
|
|
}
|
|
type = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: Int32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "index")
|
|
}
|
|
index = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "flags")
|
|
}
|
|
flags = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "InterfaceInfo", field: "change")
|
|
}
|
|
change = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
struct AddressInfo: Bindable, Equatable {
|
|
static let size = 8
|
|
|
|
var family: UInt8
|
|
var prefixLength: UInt8
|
|
var flags: UInt8
|
|
var scope: UInt8
|
|
var index: UInt32
|
|
|
|
init(
|
|
family: UInt8 = UInt8(AddressFamily.AF_UNSPEC), prefixLength: UInt8 = 32, flags: UInt8 = 0, scope: UInt8 = 0,
|
|
index: UInt32 = 0
|
|
) {
|
|
self.family = family
|
|
self.prefixLength = prefixLength
|
|
self.flags = flags
|
|
self.scope = scope
|
|
self.index = index
|
|
}
|
|
|
|
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "AddressInfo", field: "family")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: prefixLength, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "AddressInfo", field: "prefixLength")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: flags, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "AddressInfo", field: "flags")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: scope, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "AddressInfo", field: "scope")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: index, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "AddressInfo", field: "index")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "AddressInfo", field: "family")
|
|
}
|
|
family = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "AddressInfo", field: "prefixLength")
|
|
}
|
|
prefixLength = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "AddressInfo", field: "flags")
|
|
}
|
|
flags = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "AddressInfo", field: "scope")
|
|
}
|
|
scope = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "AddressInfo", field: "index")
|
|
}
|
|
index = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
struct RouteInfo: Bindable, Equatable {
|
|
static let size = 12
|
|
|
|
var family: UInt8
|
|
var dstLen: UInt8
|
|
var srcLen: UInt8
|
|
var tos: UInt8
|
|
var table: UInt8
|
|
var proto: UInt8
|
|
var scope: UInt8
|
|
var type: UInt8
|
|
var flags: UInt32
|
|
|
|
init(
|
|
family: UInt8 = UInt8(AddressFamily.AF_INET),
|
|
dstLen: UInt8,
|
|
srcLen: UInt8,
|
|
tos: UInt8,
|
|
table: UInt8,
|
|
proto: UInt8,
|
|
scope: UInt8,
|
|
type: UInt8,
|
|
flags: UInt32
|
|
) {
|
|
self.family = family
|
|
self.dstLen = dstLen
|
|
self.srcLen = srcLen
|
|
self.tos = tos
|
|
self.table = table
|
|
self.proto = proto
|
|
self.scope = scope
|
|
self.type = type
|
|
self.flags = flags
|
|
}
|
|
|
|
func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: family, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "family")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: dstLen, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "dstLen")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: srcLen, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "srcLen")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: tos, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "tos")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: table, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "table")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: proto, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "proto")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: scope, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "scope")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt8.self, value: type, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "type")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt32.self, value: flags, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RouteInfo", field: "flags")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "family")
|
|
}
|
|
family = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "dstLen")
|
|
}
|
|
dstLen = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "srcLen")
|
|
}
|
|
srcLen = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "tos")
|
|
}
|
|
tos = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "table")
|
|
}
|
|
table = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "proto")
|
|
}
|
|
proto = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "scope")
|
|
}
|
|
scope = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "type")
|
|
}
|
|
type = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RouteInfo", field: "flags")
|
|
}
|
|
flags = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
/// A route information.
|
|
public struct RTAttribute: Bindable, Equatable {
|
|
package static let size = 4
|
|
|
|
public var len: UInt16
|
|
public var type: UInt16
|
|
public var paddedLen: Int { Int(((len + 3) >> 2) << 2) }
|
|
|
|
init(len: UInt16 = 0, type: UInt16 = 0) {
|
|
self.len = len
|
|
self.type = type
|
|
}
|
|
|
|
package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: len, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "len")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt16.self, value: type, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "type")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RTAttribute", field: "len")
|
|
}
|
|
len = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "RTAttribute", field: "type")
|
|
}
|
|
type = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
/// A route information with data.
|
|
public struct RTAttributeData {
|
|
public let attribute: RTAttribute
|
|
public let data: [UInt8]
|
|
}
|
|
|
|
/// A response from the get link command.
|
|
public struct LinkResponse {
|
|
public let interfaceIndex: Int32
|
|
public let interfaceFlags: UInt32
|
|
public let interfaceType: UInt16
|
|
public let attrDatas: [RTAttributeData]
|
|
|
|
public var isLoopback: Bool {
|
|
(interfaceFlags & InterfaceFlags.IFF_LOOPBACK) != 0
|
|
}
|
|
|
|
public var isEthernet: Bool {
|
|
interfaceType == ArpHardware.ARPHRD_ETHER
|
|
&& (interfaceFlags & (InterfaceFlags.IFF_LOOPBACK | InterfaceFlags.IFF_POINTOPOINT)) == 0
|
|
}
|
|
|
|
public var address: [UInt8]? {
|
|
attrDatas
|
|
.filter { $0.attribute.type == LinkAttributeType.IFLA_ADDRESS }
|
|
.first
|
|
.map { $0.data }
|
|
}
|
|
|
|
/// Extract network interface statistics from the response attributes
|
|
public func getStatistics() throws -> LinkStatistics64? {
|
|
for attrData in attrDatas {
|
|
if attrData.attribute.type == LinkAttributeType.IFLA_STATS64 {
|
|
var stats = LinkStatistics64()
|
|
var buffer = attrData.data
|
|
_ = try stats.bindBuffer(&buffer, offset: 0)
|
|
return stats
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// Network interface statistics (64-bit version)
|
|
public struct LinkStatistics64: Bindable, Equatable {
|
|
package static let size = 23 * 8
|
|
|
|
public var rxPackets: UInt64
|
|
public var txPackets: UInt64
|
|
public var rxBytes: UInt64
|
|
public var txBytes: UInt64
|
|
public var rxErrors: UInt64
|
|
public var txErrors: UInt64
|
|
public var rxDropped: UInt64
|
|
public var txDropped: UInt64
|
|
public var multicast: UInt64
|
|
public var collisions: UInt64
|
|
public var rxLengthErrors: UInt64
|
|
public var rxOverErrors: UInt64
|
|
public var rxCrcErrors: UInt64
|
|
public var rxFrameErrors: UInt64
|
|
public var rxFifoErrors: UInt64
|
|
public var rxMissedErrors: UInt64
|
|
public var txAbortedErrors: UInt64
|
|
public var txCarrierErrors: UInt64
|
|
public var txFifoErrors: UInt64
|
|
public var txHeartbeatErrors: UInt64
|
|
public var txWindowErrors: UInt64
|
|
public var rxCompressed: UInt64
|
|
public var txCompressed: UInt64
|
|
|
|
public init() {
|
|
self.rxPackets = 0
|
|
self.txPackets = 0
|
|
self.rxBytes = 0
|
|
self.txBytes = 0
|
|
self.rxErrors = 0
|
|
self.txErrors = 0
|
|
self.rxDropped = 0
|
|
self.txDropped = 0
|
|
self.multicast = 0
|
|
self.collisions = 0
|
|
self.rxLengthErrors = 0
|
|
self.rxOverErrors = 0
|
|
self.rxCrcErrors = 0
|
|
self.rxFrameErrors = 0
|
|
self.rxFifoErrors = 0
|
|
self.rxMissedErrors = 0
|
|
self.txAbortedErrors = 0
|
|
self.txCarrierErrors = 0
|
|
self.txFifoErrors = 0
|
|
self.txHeartbeatErrors = 0
|
|
self.txWindowErrors = 0
|
|
self.rxCompressed = 0
|
|
self.txCompressed = 0
|
|
}
|
|
|
|
package func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxPackets, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxPackets")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txPackets, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txPackets")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxBytes, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxBytes")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txBytes, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txBytes")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxDropped, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxDropped")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txDropped, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txDropped")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: multicast, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "multicast")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: collisions, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "collisions")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxLengthErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxLengthErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxOverErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxOverErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxCrcErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxCrcErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxFrameErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxFrameErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxFifoErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxFifoErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxMissedErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxMissedErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txAbortedErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txAbortedErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txCarrierErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txCarrierErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txFifoErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txFifoErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txHeartbeatErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txHeartbeatErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txWindowErrors, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txWindowErrors")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: rxCompressed, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "rxCompressed")
|
|
}
|
|
guard let offset = buffer.copyIn(as: UInt64.self, value: txCompressed, offset: offset) else {
|
|
throw BindError.sendMarshalFailure(type: "LinkStatistics64", field: "txCompressed")
|
|
}
|
|
|
|
return offset
|
|
}
|
|
|
|
package mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxPackets")
|
|
}
|
|
rxPackets = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txPackets")
|
|
}
|
|
txPackets = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxBytes")
|
|
}
|
|
rxBytes = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txBytes")
|
|
}
|
|
txBytes = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxErrors")
|
|
}
|
|
rxErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txErrors")
|
|
}
|
|
txErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxDropped")
|
|
}
|
|
rxDropped = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txDropped")
|
|
}
|
|
txDropped = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "multicast")
|
|
}
|
|
multicast = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "collisions")
|
|
}
|
|
collisions = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxLengthErrors")
|
|
}
|
|
rxLengthErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxOverErrors")
|
|
}
|
|
rxOverErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxCrcErrors")
|
|
}
|
|
rxCrcErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxFrameErrors")
|
|
}
|
|
rxFrameErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxFifoErrors")
|
|
}
|
|
rxFifoErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxMissedErrors")
|
|
}
|
|
rxMissedErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txAbortedErrors")
|
|
}
|
|
txAbortedErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txCarrierErrors")
|
|
}
|
|
txCarrierErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txFifoErrors")
|
|
}
|
|
txFifoErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txHeartbeatErrors")
|
|
}
|
|
txHeartbeatErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txWindowErrors")
|
|
}
|
|
txWindowErrors = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "rxCompressed")
|
|
}
|
|
rxCompressed = value
|
|
|
|
guard let (offset, value) = buffer.copyOut(as: UInt64.self, offset: offset) else {
|
|
throw BindError.recvMarshalFailure(type: "LinkStatistics64", field: "txCompressed")
|
|
}
|
|
txCompressed = value
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|
|
/// Errors thrown when parsing netlink data.
|
|
public enum NetlinkDataError: Swift.Error, CustomStringConvertible, Equatable {
|
|
case responseError(rc: Int32)
|
|
case unsupportedPlatform
|
|
|
|
/// The description of the errors.
|
|
public var description: String {
|
|
switch self {
|
|
case .responseError(let rc):
|
|
return "netlink response indicates error, rc = \(rc)"
|
|
case .unsupportedPlatform:
|
|
return "unsupported platform"
|
|
}
|
|
}
|
|
}
|