Vendor apple/containerization with a VM-extensions forwarding patch

Switch the containerization dependency from the github URL to a vendored copy
(third_party/containerization, upstream commit 6b7b42ca) referenced by path, so
we can carry a small local patch that upstream lacks: LinuxContainer.Configuration
gains a `vmExtensions` field forwarded into VMConfiguration.extensions. Upstream
already supports VMConfiguration.extensions + the VZInstanceExtension hook, but
LinuxContainer — our only entry point — never forwarded them, so there was no way
to attach a device (e.g. a memory balloon) to a container's VM.

Tests/, docs/, examples/, images/ and the corresponding test targets are trimmed
for footprint (we never build the dependency's tests). See PATCHES.md for the full
diff vs. upstream and the re-vendoring procedure. Also adds the ContainerizationExtras
product to NucleicCore (AddressAllocator, named in the configureVZ signature).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Nucleic
2026-06-21 20:22:21 -07:00
co-authored by Claude Opus 4.8
commit 11b9825e09
280 changed files with 75699 additions and 0 deletions
@@ -0,0 +1,797 @@
//===----------------------------------------------------------------------===//
// 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
import ContainerizationOS
import Logging
/// `NetlinkSession` facilitates interacting with netlink via a provided `NetlinkSocket`. This is
/// the core high-level type offered to perform actions to the netlink surface in the kernel.
public struct NetlinkSession {
private static let receiveDataLength = 65536
private static let mtu: UInt32 = 1280
private let socket: any NetlinkSocket
private let log: Logger
/// Creates a new `NetlinkSession`.
/// - Parameters:
/// - socket: The `NetlinkSocket` to use for netlink interaction.
/// - log: The logger to use. The default value is `nil`.
public init(socket: any NetlinkSocket, log: Logger? = nil) {
self.socket = socket
self.log = log ?? Logger(label: "com.apple.containerization.netlink")
}
/// Errors that may occur during netlink interaction.
public enum Error: Swift.Error, CustomStringConvertible, Equatable {
case invalidIpAddress
case invalidPrefixLength
case unexpectedInfo(type: UInt16)
case unexpectedOffset(offset: Int, size: Int)
case unexpectedResidualPackets
case unexpectedResultSet(count: Int, expected: Int)
/// The description of the errors.
public var description: String {
switch self {
case .invalidIpAddress:
return "invalid IP address"
case .invalidPrefixLength:
return "invalid prefix length"
case .unexpectedInfo(let type):
return "unexpected response information, type = \(type)"
case .unexpectedOffset(let offset, let size):
return "unexpected buffer state, offset = \(offset), size = \(size)"
case .unexpectedResidualPackets:
return "unexpected residual response packets"
case .unexpectedResultSet(let count, let expected):
return "unexpected result set size, count = \(count), expected = \(expected)"
}
}
}
/// Performs a link set command on an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - up: The value to set the interface state to.
public func linkSet(interface: String, up: Bool, mtu: UInt32? = nil) throws {
// ip link set dev [interface] [up|down]
let interfaceIndex = try getInterfaceIndex(interface)
// build the attribute only when mtu is supplied
let attr: RTAttribute? =
(mtu != nil)
? RTAttribute(
len: UInt16(RTAttribute.size + MemoryLayout<UInt32>.size),
type: LinkAttributeType.IFLA_MTU)
: nil
let requestSize = NetlinkMessageHeader.size + InterfaceInfo.size + (attr?.paddedLen ?? 0)
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let requestHeader = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWLINK,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK,
pid: socket.pid)
requestOffset = try requestHeader.appendBuffer(&requestBuffer, offset: requestOffset)
let flags = up ? InterfaceFlags.IFF_UP : 0
let requestInfo = InterfaceInfo(
family: UInt8(AddressFamily.AF_PACKET),
index: interfaceIndex,
flags: flags,
change: InterfaceFlags.DEFAULT_CHANGE)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
if let attr = attr, let m = mtu {
requestOffset = try attr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
let newRequestOffset =
requestBuffer.copyIn(as: UInt32.self, value: m, offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_MTU")
}
requestOffset = newRequestOffset
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWLINK) { InterfaceInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Performs a link get command on an interface.
/// Returns information about the interface.
/// - Parameter interface: The name of the interface to query.
public func linkGet(interface: String? = nil, includeStats: Bool = false) throws -> [LinkResponse] {
// ip link ip show
let maskAttr = RTAttribute(
len: UInt16(RTAttribute.size + MemoryLayout<UInt32>.size), type: LinkAttributeType.IFLA_EXT_MASK)
let interfaceName = try interface.map { try getInterfaceName($0) }
let interfaceNameAttr = interfaceName.map {
RTAttribute(len: UInt16(RTAttribute.size + $0.count), type: LinkAttributeType.IFLA_IFNAME)
}
let requestSize =
NetlinkMessageHeader.size + InterfaceInfo.size + maskAttr.paddedLen + (interfaceNameAttr?.paddedLen ?? 0)
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let flags =
interface != nil ? NetlinkFlags.NLM_F_REQUEST : (NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_DUMP)
let requestHeader = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_GETLINK,
flags: flags,
pid: socket.pid)
requestOffset = try requestHeader.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = InterfaceInfo(
family: UInt8(AddressFamily.AF_PACKET),
index: 0,
flags: InterfaceFlags.IFF_UP,
change: InterfaceFlags.DEFAULT_CHANGE)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
var filters = LinkAttributeMaskFilter.RTEXT_FILTER_VF
if !includeStats {
filters |= LinkAttributeMaskFilter.RTEXT_FILTER_SKIP_STATS
}
requestOffset = try maskAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
var requestOffset = requestBuffer.copyIn(
as: UInt32.self,
value: filters,
offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_EXT_MASK")
}
if let interfaceNameAttr {
if let interfaceName {
requestOffset = try interfaceNameAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let updatedRequestOffset = requestBuffer.copyIn(buffer: interfaceName, offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_IFNAME")
}
requestOffset = updatedRequestOffset
}
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, attrDataLists) = try parseResponse(infoType: NetlinkType.RTM_NEWLINK) { InterfaceInfo() }
var linkResponses: [LinkResponse] = []
for i in 0..<infos.count {
linkResponses.append(
LinkResponse(
interfaceIndex: infos[i].index,
interfaceFlags: infos[i].flags,
interfaceType: infos[i].type,
attrDatas: attrDataLists[i])
)
}
return linkResponses
}
/// Adds an IPv4 address to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - ipv4Address: The CIDRv4 address describing the interface IP and subnet prefix length.
public func addressAdd(interface: String, ipv4Address: CIDRv4) throws {
// ip addr add [addr] dev [interface]
// ip address {add|change|replace} IFADDR dev IFNAME [ LIFETIME ] [ CONFFLAG-LIST ]
// IFADDR := PREFIX | ADDR peer PREFIX
// [ broadcast ADDR ] [ anycast ADDR ]
// [ label IFNAME ] [ scope SCOPE-ID ] [ metric METRIC ]
// SCOPE-ID := [ host | link | global | NUMBER ]
// CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG
// CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]
// LIFETIME := [ valid_lft LFT ] [ preferred_lft LFT ]
// LFT := forever | SECONDS
let interfaceIndex = try getInterfaceIndex(interface)
let ipAddressBytes = ipv4Address.address.bytes
let addressAttrSize = RTAttribute.size + MemoryLayout<UInt8>.size * ipAddressBytes.count
let requestSize = NetlinkMessageHeader.size + AddressInfo.size + 2 * addressAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWADDR,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
seq: 0,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = AddressInfo(
family: UInt8(AddressFamily.AF_INET),
prefixLength: ipv4Address.prefix.length,
flags: 0,
scope: NetlinkScope.RT_SCOPE_UNIVERSE,
index: UInt32(interfaceIndex))
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
let ipLocalAttr = RTAttribute(len: UInt16(addressAttrSize), type: AddressAttributeType.IFA_LOCAL)
requestOffset = try ipLocalAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard var requestOffset = requestBuffer.copyIn(buffer: ipAddressBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFA_LOCAL")
}
let ipAddressAttr = RTAttribute(len: UInt16(addressAttrSize), type: AddressAttributeType.IFA_ADDRESS)
requestOffset = try ipAddressAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let requestOffset = requestBuffer.copyIn(buffer: ipAddressBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFA_ADDRESS")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWLINK) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Adds an IPv6 address to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - ipv6Address: The CIDRv6 address describing the interface IP and subnet prefix length.
public func addressAdd(interface: String, ipv6Address: CIDRv6) throws {
let interfaceIndex = try getInterfaceIndex(interface)
let ipAddressBytes = ipv6Address.address.bytes
let addressAttrSize = RTAttribute.size + MemoryLayout<UInt8>.size * ipAddressBytes.count
let requestSize = NetlinkMessageHeader.size + AddressInfo.size + addressAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWADDR,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
seq: 0,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = AddressInfo(
family: UInt8(AddressFamily.AF_INET6),
prefixLength: ipv6Address.prefix.length,
flags: AddressFlags.IFA_F_PERMANENT | AddressFlags.IFA_F_NODAD,
scope: NetlinkScope.RT_SCOPE_UNIVERSE,
index: UInt32(interfaceIndex))
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
let ipAddressAttr = RTAttribute(len: UInt16(addressAttrSize), type: AddressAttributeType.IFA_ADDRESS)
requestOffset = try ipAddressAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let requestOffset = requestBuffer.copyIn(buffer: ipAddressBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFA_ADDRESS")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWADDR) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Adds an IPv4 route to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - dstIpv4Addr: The CIDRv4 address describing the gateway IP and subnet prefix length.
/// - srcIpv4Addr: The source IPv4 address to route from.
public func routeAdd(
interface: String,
dstIpv4Addr: CIDRv4,
srcIpv4Addr: IPv4Address?
) throws {
// ip route add [dest-cidr] dev [interface] [src [src-addr]] proto kernel
let interfaceIndex = try getInterfaceIndex(interface)
let dstAddrBytes = dstIpv4Addr.address.bytes
let dstAddrAttrSize = RTAttribute.size + dstAddrBytes.count
let srcAddrAttrSize: Int
if let srcIpv4Addr {
let srcAddrBytes = srcIpv4Addr.bytes
srcAddrAttrSize = RTAttribute.size + srcAddrBytes.count
} else {
srcAddrAttrSize = 0
}
let interfaceAttrSize = RTAttribute.size + MemoryLayout<UInt32>.size
let requestSize =
NetlinkMessageHeader.size + RouteInfo.size + dstAddrAttrSize + srcAddrAttrSize + interfaceAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWROUTE,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = RouteInfo(
family: UInt8(AddressFamily.AF_INET),
dstLen: dstIpv4Addr.prefix.length,
srcLen: 0,
tos: 0,
table: RouteTable.MAIN,
proto: RouteProtocol.KERNEL,
scope: RouteScope.LINK,
type: RouteType.UNICAST,
flags: 0)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
let dstAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.DST)
requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard var requestOffset = requestBuffer.copyIn(buffer: dstAddrBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_DST")
}
if let srcIpv4Addr {
let srcAddrBytes = srcIpv4Addr.bytes
let srcAddrAttr = RTAttribute(len: UInt16(srcAddrAttrSize), type: RouteAttributeType.PREFSRC)
requestOffset = try srcAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let newOffset = requestBuffer.copyIn(buffer: srcAddrBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_PREFSRC")
}
requestOffset = newOffset
}
let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF)
requestOffset = try interfaceAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
let requestOffset = requestBuffer.copyIn(
as: UInt32.self,
value: UInt32(interfaceIndex),
offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWLINK) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Adds a default IPv4 route to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - ipv4Gateway: The gateway address, or nil.
public func routeAddDefault(
interface: String,
ipv4Gateway: IPv4Address?
) throws {
// ip route add default via [gateway] dev [interface] or
// ip route add default dev [interface]
let dstAddrBytes = ipv4Gateway?.bytes
let dstAddrAttrSize: Int
if let dstAddrBytes {
dstAddrAttrSize = RTAttribute.size + dstAddrBytes.count
} else {
dstAddrAttrSize = 0
}
let interfaceAttrSize = RTAttribute.size + MemoryLayout<UInt32>.size
let interfaceIndex = try getInterfaceIndex(interface)
let requestSize = NetlinkMessageHeader.size + RouteInfo.size + dstAddrAttrSize + interfaceAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWROUTE,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = RouteInfo(
family: UInt8(AddressFamily.AF_INET),
dstLen: 0,
srcLen: 0,
tos: 0,
table: RouteTable.MAIN,
proto: RouteProtocol.BOOT,
scope: ipv4Gateway != nil ? RouteScope.UNIVERSE : RouteScope.LINK,
type: RouteType.UNICAST,
flags: 0)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
if let dstAddrBytes {
let dstAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.GATEWAY)
requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let newOffset = requestBuffer.copyIn(buffer: dstAddrBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_GATEWAY")
}
requestOffset = newOffset
}
let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF)
requestOffset = try interfaceAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
let requestOffset = requestBuffer.copyIn(
as: UInt32.self,
value: UInt32(interfaceIndex),
offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWLINK) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Adds an IPv6 route to an interface. Used to install an on-link host
/// route (typically a /128) to a gateway that lives outside the interface's
/// subnet, so the kernel will accept the v6 default route. The chosen
/// `proto STATIC, scope LINK` matches what `iproute2` emits for explicit
/// `ip -6 route add <addr>/128 dev <iface>`.
/// - Parameters:
/// - interface: The name of the interface.
/// - dstIpv6Addr: The CIDRv6 address describing the destination network and prefix length.
/// - srcIpv6Addr: The source IPv6 address to route from.
public func routeAdd(
interface: String,
dstIpv6Addr: CIDRv6,
srcIpv6Addr: IPv6Address?
) throws {
let interfaceIndex = try getInterfaceIndex(interface)
let dstAddrBytes = dstIpv6Addr.address.bytes
let dstAddrAttrSize = RTAttribute.size + dstAddrBytes.count
let srcAddrAttrSize: Int
if let srcIpv6Addr {
let srcAddrBytes = srcIpv6Addr.bytes
srcAddrAttrSize = RTAttribute.size + srcAddrBytes.count
} else {
srcAddrAttrSize = 0
}
let interfaceAttrSize = RTAttribute.size + MemoryLayout<UInt32>.size
let requestSize =
NetlinkMessageHeader.size + RouteInfo.size + dstAddrAttrSize + srcAddrAttrSize + interfaceAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWROUTE,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = RouteInfo(
family: UInt8(AddressFamily.AF_INET6),
dstLen: dstIpv6Addr.prefix.length,
srcLen: 0,
tos: 0,
table: RouteTable.MAIN,
proto: RouteProtocol.STATIC,
scope: RouteScope.LINK,
type: RouteType.UNICAST,
flags: 0)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
let dstAddrAttr = RTAttribute(len: UInt16(dstAddrAttrSize), type: RouteAttributeType.DST)
requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard var requestOffset = requestBuffer.copyIn(buffer: dstAddrBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_DST")
}
if let srcIpv6Addr {
let srcAddrBytes = srcIpv6Addr.bytes
let srcAddrAttr = RTAttribute(len: UInt16(srcAddrAttrSize), type: RouteAttributeType.PREFSRC)
requestOffset = try srcAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard let newOffset = requestBuffer.copyIn(buffer: srcAddrBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_PREFSRC")
}
requestOffset = newOffset
}
let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF)
requestOffset = try interfaceAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
let requestOffset = requestBuffer.copyIn(
as: UInt32.self,
value: UInt32(interfaceIndex),
offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWROUTE) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
/// Adds a default IPv6 route to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - ipv6Gateway: The gateway address.
public func routeAddDefault(
interface: String,
ipv6Gateway: IPv6Address
) throws {
let gatewayBytes = ipv6Gateway.bytes
let gatewaySize = RTAttribute.size + gatewayBytes.count
let interfaceAttrSize = RTAttribute.size + MemoryLayout<UInt32>.size
let interfaceIndex = try getInterfaceIndex(interface)
let requestSize = NetlinkMessageHeader.size + RouteInfo.size + gatewaySize + interfaceAttrSize
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
var requestOffset = 0
let header = NetlinkMessageHeader(
len: UInt32(requestBuffer.count),
type: NetlinkType.RTM_NEWROUTE,
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK | NetlinkFlags.NLM_F_EXCL
| NetlinkFlags.NLM_F_CREATE,
pid: socket.pid)
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
let requestInfo = RouteInfo(
family: UInt8(AddressFamily.AF_INET6),
dstLen: 0,
srcLen: 0,
tos: 0,
table: RouteTable.MAIN,
proto: RouteProtocol.BOOT,
scope: RouteScope.UNIVERSE,
type: RouteType.UNICAST,
flags: 0)
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
let dstAddrAttr = RTAttribute(len: UInt16(gatewaySize), type: RouteAttributeType.GATEWAY)
requestOffset = try dstAddrAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard var requestOffset = requestBuffer.copyIn(buffer: gatewayBytes, offset: requestOffset) else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_GATEWAY")
}
let interfaceAttr = RTAttribute(len: UInt16(interfaceAttrSize), type: RouteAttributeType.OIF)
requestOffset = try interfaceAttr.appendBuffer(&requestBuffer, offset: requestOffset)
guard
let requestOffset = requestBuffer.copyIn(
as: UInt32.self,
value: UInt32(interfaceIndex),
offset: requestOffset)
else {
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "RTA_OIF")
}
guard requestOffset == requestSize else {
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
}
try sendRequest(buffer: &requestBuffer)
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_NEWROUTE) { AddressInfo() }
guard infos.count == 0 else {
throw Error.unexpectedResultSet(count: infos.count, expected: 0)
}
}
private func getInterfaceName(_ interface: String) throws -> [UInt8] {
guard let interfaceNameData = interface.data(using: .utf8) else {
throw BindError.sendMarshalFailure(type: "String", field: "interface")
}
var interfaceName = [UInt8](interfaceNameData)
interfaceName.append(0)
while interfaceName.count % MemoryLayout<UInt32>.size != 0 {
interfaceName.append(0)
}
return interfaceName
}
private func getInterfaceIndex(_ interface: String) throws -> Int32 {
let linkResponses = try linkGet(interface: interface)
guard linkResponses.count == 1 else {
throw Error.unexpectedResultSet(count: linkResponses.count, expected: 1)
}
return linkResponses[0].interfaceIndex
}
private func sendRequest(buffer: inout [UInt8]) throws {
log.trace("SEND-LENGTH: \(buffer.count)")
log.trace("SEND-DUMP: \(buffer[0..<buffer.count].hexEncodedString())")
let sendLength = try socket.send(buf: &buffer, len: buffer.count, flags: 0)
if sendLength != buffer.count {
log.warning("sent length \(sendLength) not equal to packet length \(buffer.count)")
}
}
private func receiveResponse() throws -> ([UInt8], Int) {
var buffer = [UInt8](repeating: 0, count: Self.receiveDataLength)
let size = try socket.recv(buf: &buffer, len: Self.receiveDataLength, flags: 0)
log.trace("RECV-LENGTH: \(size)")
log.trace("RECV-DUMP: \(buffer[0..<size].hexEncodedString())")
return (buffer, size)
}
private func parseResponse<T: Bindable>(infoType: UInt16? = nil, _ infoProvider: () -> T) throws -> (
[T], [[RTAttributeData]]
) {
var infos: [T] = []
var attrDataLists: [[RTAttributeData]] = []
var moreResponses = false
repeat {
var (buffer, size) = try receiveResponse()
var offset = 0
// A single buffer may contain multiple netlink messages
while offset < size {
let messageStart = offset
let header: NetlinkMessageHeader
(header, offset) = try parseHeader(buffer: &buffer, offset: offset)
if let infoType {
if header.type == infoType {
log.trace(
"RECV-INFO-DUMP: dump = \(buffer[offset..<offset + InterfaceInfo.size].hexEncodedString())")
var info = infoProvider()
offset = try info.bindBuffer(&buffer, offset: offset)
log.trace("RECV-INFO: \(info)")
// Calculate the number of bytes remaining in THIS message
let messageEnd = messageStart + Int(header.len)
let attributeBytes = messageEnd - offset
let attrDatas: [RTAttributeData]
(attrDatas, offset) = try parseAttributes(
buffer: &buffer,
offset: offset,
residualCount: attributeBytes)
infos.append(info)
attrDataLists.append(attrDatas)
} else {
// Skip this message - advance offset to the end of the message
offset = messageStart + Int(header.len)
}
} else if header.type != NetlinkType.NLMSG_DONE && header.type != NetlinkType.NLMSG_ERROR
&& header.type != NetlinkType.NLMSG_NOOP
{
throw Error.unexpectedInfo(type: header.type)
}
moreResponses = header.moreResponses
}
guard offset == size else {
throw Error.unexpectedOffset(offset: offset, size: size)
}
} while moreResponses
return (infos, attrDataLists)
}
private func parseErrorCode(buffer: inout [UInt8], offset: Int) throws -> (Int32, Int) {
guard let errorPtr = buffer.bind(as: Int32.self, offset: offset) else {
throw BindError.recvMarshalFailure(type: "NetlinkErrorMessage", field: "error")
}
let rc = errorPtr.pointee
log.trace("RECV-ERR-CODE: \(rc)")
return (rc, offset + MemoryLayout<Int32>.size)
}
private func parseErrorResponse(buffer: inout [UInt8], offset: Int) throws -> Int {
var (rc, offset) = try parseErrorCode(buffer: &buffer, offset: offset)
log.trace(
"RECV-ERR-HEADER-DUMP: dump = \(buffer[offset..<offset + NetlinkMessageHeader.size].hexEncodedString())")
var header = NetlinkMessageHeader()
offset = try header.bindBuffer(&buffer, offset: offset)
log.trace("RECV-ERR-HEADER: \(header))")
guard rc == 0 else {
throw NetlinkDataError.responseError(rc: rc)
}
return offset
}
private func parseHeader(buffer: inout [UInt8], offset: Int) throws -> (NetlinkMessageHeader, Int) {
log.trace("RECV-HEADER-DUMP: dump = \(buffer[offset..<offset + NetlinkMessageHeader.size].hexEncodedString())")
var header = NetlinkMessageHeader()
var offset = try header.bindBuffer(&buffer, offset: offset)
log.trace("RECV-HEADER: \(header)")
switch header.type {
case NetlinkType.NLMSG_ERROR:
offset = try parseErrorResponse(buffer: &buffer, offset: offset)
case NetlinkType.NLMSG_DONE:
let rc: Int32
(rc, offset) = try parseErrorCode(buffer: &buffer, offset: offset)
guard rc == 0 else {
throw NetlinkDataError.responseError(rc: rc)
}
default:
break
}
return (header, offset)
}
private func parseAttributes(buffer: inout [UInt8], offset: Int, residualCount: Int) throws -> (
[RTAttributeData], Int
) {
var attrDatas: [RTAttributeData] = []
var offset = offset
var residualCount = residualCount
log.trace("RECV-RESIDUAL: \(residualCount)")
while residualCount > 0 {
var attr = RTAttribute()
log.trace(" RECV-ATTR-DUMP: dump = \(buffer[offset..<offset + RTAttribute.size].hexEncodedString())")
offset = try attr.bindBuffer(&buffer, offset: offset)
log.trace(" RECV-ATTR: len = \(attr.len) type = \(attr.type)")
let dataLen = Int(attr.len) - RTAttribute.size
if dataLen >= 0 {
log.trace(" RECV-ATTR-DATA-DUMP: dump = \(buffer[offset..<offset + dataLen].hexEncodedString())")
attrDatas.append(RTAttributeData(attribute: attr, data: Array(buffer[offset..<offset + dataLen])))
} else {
attrDatas.append(RTAttributeData(attribute: attr, data: []))
}
residualCount -= Int(attr.paddedLen)
offset += attr.paddedLen - RTAttribute.size
log.trace("RECV-RESIDUAL: \(residualCount)")
}
return (attrDatas, offset)
}
}
@@ -0,0 +1,141 @@
//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
/// A protocol for interacting with a netlink socket.
public protocol NetlinkSocket {
var pid: UInt32 { get }
func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int
func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int
}
/// A netlink socket provider.
public typealias NetlinkSocketProvider = () throws -> any NetlinkSocket
/// Errors thrown when interacting with a netlink socket.
public enum NetlinkSocketError: Swift.Error, CustomStringConvertible, Equatable {
case socketFailure(rc: Int32)
case bindFailure(rc: Int32)
case sendFailure(rc: Int32)
case recvFailure(rc: Int32)
case notImplemented
/// The description of the errors.
public var description: String {
switch self {
case .socketFailure(let rc):
return "could not create netlink socket, rc = \(rc)"
case .bindFailure(let rc):
return "could not bind netlink socket, rc = \(rc)"
case .sendFailure(let rc):
return "could not send netlink packet, rc = \(rc)"
case .recvFailure(let rc):
return "could not receive netlink packet, rc = \(rc)"
case .notImplemented:
return "socket function not implemented for platform"
}
}
}
#if os(Linux)
#if canImport(Musl)
import Musl
let osSocket = Musl.socket
let osBind = Musl.bind
let osSend = Musl.send
let osRecv = Musl.recv
#elseif canImport(Glibc)
import Glibc
let osSocket = Glibc.socket
let osBind = Glibc.bind
let osSend = Glibc.send
let osRecv = Glibc.recv
#endif
/// A default implementation of `NetlinkSocket`.
public class DefaultNetlinkSocket: NetlinkSocket {
private let sockfd: Int32
/// The process identifier of the process creating this socket.
public let pid: UInt32
/// Creates a new instance.
public init() throws {
pid = UInt32(getpid())
sockfd = osSocket(Int32(AddressFamily.AF_NETLINK), SocketType.SOCK_RAW, NetlinkProtocol.NETLINK_ROUTE)
guard sockfd >= 0 else {
throw NetlinkSocketError.socketFailure(rc: errno)
}
let addr = SockaddrNetlink(family: AddressFamily.AF_NETLINK, pid: pid)
var buffer = [UInt8](repeating: 0, count: SockaddrNetlink.size)
_ = try addr.appendBuffer(&buffer, offset: 0)
guard let ptr = buffer.bind(as: sockaddr.self, size: buffer.count) else {
throw NetlinkSocketError.bindFailure(rc: 0)
}
guard osBind(sockfd, ptr, UInt32(buffer.count)) >= 0 else {
throw NetlinkSocketError.bindFailure(rc: errno)
}
}
deinit {
close(sockfd)
}
/// Sends a request to a netlink socket.
/// Returns the number of bytes sent.
/// - Parameters:
/// - buf: The buffer to send.
/// - len: The length of the buffer to send.
/// - flags: The send flags.
public func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int {
let count = osSend(sockfd, buf, len, flags)
guard count >= 0 else {
throw NetlinkSocketError.sendFailure(rc: errno)
}
return count
}
/// Receives a response from a netlink socket.
/// Returns the number of bytes received.
/// - Parameters:
/// - buf: The buffer to receive into.
/// - len: The maximum number of bytes to receive.
/// - flags: The receive flags.
public func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int {
let count = osRecv(sockfd, buf, len, flags)
guard count >= 0 else {
throw NetlinkSocketError.recvFailure(rc: errno)
}
return count
}
}
#else
public class DefaultNetlinkSocket: NetlinkSocket {
public var pid: UInt32 { 0 }
public init() throws {}
public func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int {
throw NetlinkSocketError.notImplemented
}
public func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int {
throw NetlinkSocketError.notImplemented
}
}
#endif
+882
View File
@@ -0,0 +1,882 @@
//===----------------------------------------------------------------------===//
// 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_STATS64: UInt16 = 23
static let IFLA_EXT_MASK: UInt16 = 29
}
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"
}
}
}