Add documentation for netlink (#21)

This commit is contained in:
Dmitry Kovba
2025-06-05 16:16:47 -07:00
committed by Kathryn Baldauf
parent 9ac3874008
commit 4eba0fa572
3 changed files with 67 additions and 19 deletions
@@ -19,20 +19,23 @@ 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.
/// `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 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
@@ -41,6 +44,7 @@ public struct NetlinkSession {
case unexpectedResidualPackets
case unexpectedResultSet(count: Int, expected: Int)
/// The description of the errors.
public var description: String {
switch self {
case .invalidIpAddress:
@@ -59,8 +63,12 @@ public struct NetlinkSession {
}
}
/// ip link set dev [interface] [up|down]
/// 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) throws {
// ip link set dev [interface] [up|down]
let interfaceIndex = try getInterfaceIndex(interface)
let requestSize = NetlinkMessageHeader.size + InterfaceInfo.size
var requestBuffer = [UInt8](repeating: 0, count: requestSize)
@@ -92,8 +100,11 @@ public struct NetlinkSession {
}
}
/// ip link ip show
/// 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) 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) }
@@ -157,17 +168,21 @@ public struct NetlinkSession {
return linkResponses
}
/// 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
/// Adds an IPv4 address to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - address: The IPv4 address to add.
public func addressAdd(interface: String, address: String) 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 parsed = try parseCIDR(cidr: address)
let interfaceIndex = try getInterfaceIndex(interface)
let ipAddressBytes = try IPv4Address(parsed.address).networkBytes
@@ -231,12 +246,17 @@ public struct NetlinkSession {
return (address, prefixLength)
}
/// ip route add [dest-cidr] dev [interface] src [src-addr] proto kernel
/// Adds a route to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - destinationAddress: The destination address to route to.
/// - srcAddr: The source address to route from.
public func routeAdd(
interface: String,
destinationAddress: String,
srcAddr: String
) throws {
// ip route add [dest-cidr] dev [interface] src [src-addr] proto kernel
let parsed = try parseCIDR(cidr: destinationAddress)
let interfaceIndex = try getInterfaceIndex(interface)
let dstAddrBytes = try IPv4Address(parsed.address).networkBytes
@@ -303,11 +323,15 @@ public struct NetlinkSession {
}
}
/// ip route add default via [dst-address] src [src-address]
/// Adds a default route to an interface.
/// - Parameters:
/// - interface: The name of the interface.
/// - gateway: The gateway address.
public func routeAddDefault(
interface: String,
gateway: String
) throws {
// ip route add default via [dst-address] src [src-address]
let dstAddrBytes = try IPv4Address(gateway).networkBytes
let dstAddrAttrSize = RTAttribute.size + dstAddrBytes.count
@@ -15,14 +15,17 @@
// 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)
@@ -30,6 +33,7 @@ public enum NetlinkSocketError: Swift.Error, CustomStringConvertible, Equatable
case recvFailure(rc: Int32)
case notImplemented
/// The description of the errors.
public var description: String {
switch self {
case .socketFailure(let rc):
@@ -53,11 +57,14 @@ let osBind = Musl.bind
let osSend = Musl.send
let osRecv = Musl.recv
/// 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)
@@ -80,6 +87,12 @@ public class DefaultNetlinkSocket: NetlinkSocket {
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 {
@@ -89,6 +102,12 @@ public class DefaultNetlinkSocket: NetlinkSocket {
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 {
@@ -519,6 +519,7 @@ struct RouteInfo: Bindable {
}
}
/// A route information.
public struct RTAttribute: Bindable {
static let size = 4
@@ -557,22 +558,26 @@ public struct RTAttribute: Bindable {
}
}
/// 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 attrDatas: [RTAttributeData]
}
/// Errors thrown when parsing netlink data.
public enum NetlinkDataError: Swift.Error, CustomStringConvertible, Equatable {
case sendMarshalFailure
case recvUnmarshalFailure
case responseError(rc: Int32)
case unsupportedPlatform
/// The description of the errors.
public var description: String {
switch self {
case .sendMarshalFailure: