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:
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user