680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
1007 lines
44 KiB
Swift
1007 lines
44 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import ContainerizationExtras
|
|
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)
|
|
}
|
|
}
|
|
|
|
/// Set link attributes (MAC and/or bridge master) on an existing interface.
|
|
/// Either argument may be omitted; if both are nil this is a no-op.
|
|
///
|
|
/// Sends a single `RTM_NEWLINK` carrying any of:
|
|
/// - `IFLA_ADDRESS` — the new hardware address (6 bytes for an Ethernet MAC).
|
|
/// - `IFLA_MASTER` — the index of the bridge to enslave the link to. The
|
|
/// bridge is identified by name; the index is resolved internally.
|
|
///
|
|
/// - Parameters:
|
|
/// - interface: The name of the interface to update.
|
|
/// - macAddress: If non-nil, the new MAC address.
|
|
/// - master: If non-nil, the name of a bridge to enslave the interface to.
|
|
public func linkSetAttributes(
|
|
interface: String,
|
|
macAddress: MACAddress? = nil,
|
|
master: String? = nil
|
|
) throws {
|
|
if macAddress == nil && master == nil {
|
|
return
|
|
}
|
|
|
|
let interfaceIndex = try getInterfaceIndex(interface)
|
|
|
|
var masterIndex: Int32? = nil
|
|
if let master {
|
|
masterIndex = try getInterfaceIndex(master)
|
|
}
|
|
|
|
// Build the attribute list. MAC is 6 raw bytes; master is a 4-byte
|
|
// integer holding the bridge's interface index.
|
|
let macAttr: RTAttribute? =
|
|
(macAddress != nil)
|
|
? RTAttribute(
|
|
len: UInt16(RTAttribute.size + 6),
|
|
type: LinkAttributeType.IFLA_ADDRESS)
|
|
: nil
|
|
let masterAttr: RTAttribute? =
|
|
(masterIndex != nil)
|
|
? RTAttribute(
|
|
len: UInt16(RTAttribute.size + MemoryLayout<Int32>.size),
|
|
type: LinkAttributeType.IFLA_MASTER)
|
|
: nil
|
|
|
|
let requestSize =
|
|
NetlinkMessageHeader.size
|
|
+ InterfaceInfo.size
|
|
+ (macAttr?.paddedLen ?? 0)
|
|
+ (masterAttr?.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)
|
|
|
|
// No flag changes — passing 0/0 means "do not modify IFF_* flags".
|
|
let requestInfo = InterfaceInfo(
|
|
family: UInt8(AddressFamily.AF_PACKET),
|
|
index: interfaceIndex,
|
|
flags: 0,
|
|
change: 0)
|
|
requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
|
|
if let macAttr, let macAddress {
|
|
requestOffset = try macAttr.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
for byte in macAddress.bytes {
|
|
guard let next = requestBuffer.copyIn(as: UInt8.self, value: byte, offset: requestOffset) else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_ADDRESS")
|
|
}
|
|
requestOffset = next
|
|
}
|
|
// Pad attribute payload to 4-byte boundary (NLA_ALIGN).
|
|
let payloadLen = 6
|
|
let padded = ((payloadLen + 3) >> 2) << 2
|
|
requestOffset += padded - payloadLen
|
|
}
|
|
|
|
if let masterAttr, let masterIndex {
|
|
requestOffset = try masterAttr.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
guard
|
|
let next = requestBuffer.copyIn(as: Int32.self, value: masterIndex, offset: requestOffset)
|
|
else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_MASTER")
|
|
}
|
|
requestOffset = next
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/// Create a Linux bridge link via `RTM_NEWLINK` carrying
|
|
/// `IFLA_LINKINFO/IFLA_INFO_KIND="bridge"`.
|
|
///
|
|
/// Sends `NLM_F_CREATE | NLM_F_EXCL`, so the kernel returns `EEXIST` if a
|
|
/// link with the same name already exists. Callers wanting idempotent
|
|
/// creation should catch and inspect the thrown error.
|
|
public func linkAddBridge(name: String) throws {
|
|
let nameBytes = Array(name.utf8) + [0]
|
|
let ifnameAttr = RTAttribute(
|
|
len: UInt16(RTAttribute.size + nameBytes.count),
|
|
type: LinkAttributeType.IFLA_IFNAME)
|
|
|
|
let kindBytes = Array("bridge".utf8) + [0]
|
|
let kindAttr = RTAttribute(
|
|
len: UInt16(RTAttribute.size + kindBytes.count),
|
|
type: LinkInfoAttributeType.IFLA_INFO_KIND)
|
|
// IFLA_LINKINFO is a nest containing IFLA_INFO_KIND.
|
|
let linkInfoAttr = RTAttribute(
|
|
len: UInt16(RTAttribute.size + kindAttr.paddedLen),
|
|
type: LinkAttributeType.IFLA_LINKINFO)
|
|
|
|
let requestSize =
|
|
NetlinkMessageHeader.size
|
|
+ InterfaceInfo.size
|
|
+ ifnameAttr.paddedLen
|
|
+ linkInfoAttr.paddedLen
|
|
|
|
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
|
|
var requestOffset = 0
|
|
|
|
let header = NetlinkMessageHeader(
|
|
len: UInt32(requestBuffer.count),
|
|
type: NetlinkType.RTM_NEWLINK,
|
|
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK
|
|
| NetlinkFlags.NLM_F_CREATE | NetlinkFlags.NLM_F_EXCL,
|
|
pid: socket.pid)
|
|
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
|
|
let info = InterfaceInfo(
|
|
family: UInt8(AddressFamily.AF_UNSPEC),
|
|
index: 0,
|
|
flags: 0,
|
|
change: 0)
|
|
requestOffset = try info.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
|
|
// IFLA_IFNAME
|
|
requestOffset = try ifnameAttr.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
guard let next = requestBuffer.copyIn(buffer: nameBytes, offset: requestOffset) else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_IFNAME")
|
|
}
|
|
// Pad NUL-terminated name to NLA 4-byte boundary.
|
|
requestOffset = next + (ifnameAttr.paddedLen - RTAttribute.size - nameBytes.count)
|
|
|
|
// IFLA_LINKINFO -> IFLA_INFO_KIND
|
|
requestOffset = try linkInfoAttr.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
requestOffset = try kindAttr.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
guard let after = requestBuffer.copyIn(buffer: kindBytes, offset: requestOffset) else {
|
|
throw BindError.sendMarshalFailure(type: "RTAttribute", field: "IFLA_INFO_KIND")
|
|
}
|
|
requestOffset = after + (kindAttr.paddedLen - RTAttribute.size - kindBytes.count)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/// Remove a link by name via `RTM_DELLINK`.
|
|
///
|
|
/// Throws on netlink error. Callers wanting idempotent removal should
|
|
/// catch and inspect the thrown error (e.g. `ENODEV` ⇒ already gone).
|
|
public func linkDel(name: String) throws {
|
|
let interfaceIndex = try getInterfaceIndex(name)
|
|
let requestSize = NetlinkMessageHeader.size + InterfaceInfo.size
|
|
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
|
|
var requestOffset = 0
|
|
|
|
let header = NetlinkMessageHeader(
|
|
len: UInt32(requestBuffer.count),
|
|
type: NetlinkType.RTM_DELLINK,
|
|
flags: NetlinkFlags.NLM_F_REQUEST | NetlinkFlags.NLM_F_ACK,
|
|
pid: socket.pid)
|
|
requestOffset = try header.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
|
|
let info = InterfaceInfo(
|
|
family: UInt8(AddressFamily.AF_UNSPEC),
|
|
index: interfaceIndex,
|
|
flags: 0,
|
|
change: 0)
|
|
requestOffset = try info.appendBuffer(&requestBuffer, offset: requestOffset)
|
|
|
|
guard requestOffset == requestSize else {
|
|
throw Error.unexpectedOffset(offset: requestOffset, size: requestSize)
|
|
}
|
|
|
|
try sendRequest(buffer: &requestBuffer)
|
|
let (infos, _) = try parseResponse(infoType: NetlinkType.RTM_DELLINK) { 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_NEWADDR) { 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_NEWROUTE) { 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_NEWROUTE) { 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)
|
|
}
|
|
}
|