Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 43 additions & 19 deletions Sources/ContainerizationNetlink/NetlinkSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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
Comment thread
dkovba marked this conversation as resolved.
/// 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions Sources/ContainerizationNetlink/NetlinkSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@
// 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):
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions Sources/ContainerizationNetlink/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ struct RouteInfo: Bindable {
}
}

/// A route information.
public struct RTAttribute: Bindable {
static let size = 4

Expand Down Expand Up @@ -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:
Expand Down