diff --git a/Sources/ContainerizationNetlink/NetlinkSession.swift b/Sources/ContainerizationNetlink/NetlinkSession.swift index e3103694..383df15b 100644 --- a/Sources/ContainerizationNetlink/NetlinkSession.swift +++ b/Sources/ContainerizationNetlink/NetlinkSession.swift @@ -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.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 diff --git a/Sources/ContainerizationNetlink/NetlinkSocket.swift b/Sources/ContainerizationNetlink/NetlinkSocket.swift index b3446095..8993ae5a 100644 --- a/Sources/ContainerizationNetlink/NetlinkSocket.swift +++ b/Sources/ContainerizationNetlink/NetlinkSocket.swift @@ -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 { diff --git a/Sources/ContainerizationNetlink/Types.swift b/Sources/ContainerizationNetlink/Types.swift index e7921ffe..d119f7f1 100644 --- a/Sources/ContainerizationNetlink/Types.swift +++ b/Sources/ContainerizationNetlink/Types.swift @@ -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: