diff --git a/Sources/Containerization/Agent/Vminitd.swift b/Sources/Containerization/Agent/Vminitd.swift index 5267624b..0b72b49e 100644 --- a/Sources/Containerization/Agent/Vminitd.swift +++ b/Sources/Containerization/Agent/Vminitd.swift @@ -198,10 +198,11 @@ extension Vminitd: VirtualMachineAgent { _ = try await client.deleteProcess(request) } - public func up(name: String) async throws { + public func up(name: String, mtu: UInt32? = nil) async throws { let request = Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest.with { $0.interface = name $0.up = true + if let mtu { $0.mtu = mtu } } _ = try await client.ipLinkSet(request) } diff --git a/Sources/Containerization/LinuxContainer.swift b/Sources/Containerization/LinuxContainer.swift index 141c3057..98b7e928 100644 --- a/Sources/Containerization/LinuxContainer.swift +++ b/Sources/Containerization/LinuxContainer.swift @@ -504,7 +504,7 @@ extension LinuxContainer { for (index, i) in self.interfaces.enumerated() { let name = "eth\(index)" try await agent.addressAdd(name: name, address: i.address) - try await agent.up(name: name) + try await agent.up(name: name, mtu: 1280) if let gateway = i.gateway { try await agent.routeAddDefault(name: name, gateway: gateway) } diff --git a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift index a1ac40e6..0cd9c8c3 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -658,9 +658,20 @@ public struct Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: Sendable { public var up: Bool = false + public var mtu: UInt32 { + get {return _mtu ?? 0} + set {_mtu = newValue} + } + /// Returns true if `mtu` has been explicitly set. + public var hasMtu: Bool {return self._mtu != nil} + /// Clears the value of `mtu`. Subsequent reads from it will return its default value. + public mutating func clearMtu() {self._mtu = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _mtu: UInt32? = nil } public struct Com_Apple_Containerization_Sandbox_V3_IpLinkSetResponse: Sendable { @@ -1992,6 +2003,7 @@ extension Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: SwiftProtobuf. public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "interface"), 2: .same(proto: "up"), + 3: .same(proto: "mtu"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2002,24 +2014,33 @@ extension Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: SwiftProtobuf. switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self.interface) }() case 2: try { try decoder.decodeSingularBoolField(value: &self.up) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._mtu) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.interface.isEmpty { try visitor.visitSingularStringField(value: self.interface, fieldNumber: 1) } if self.up != false { try visitor.visitSingularBoolField(value: self.up, fieldNumber: 2) } + try { if let v = self._mtu { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest, rhs: Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest) -> Bool { if lhs.interface != rhs.interface {return false} if lhs.up != rhs.up {return false} + if lhs._mtu != rhs._mtu {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/Containerization/SandboxContext/SandboxContext.proto b/Sources/Containerization/SandboxContext/SandboxContext.proto index bd3a080e..4b4dba23 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.proto +++ b/Sources/Containerization/SandboxContext/SandboxContext.proto @@ -193,6 +193,7 @@ message MkdirResponse {} message IpLinkSetRequest { string interface = 1; bool up = 2; + optional uint32 mtu = 3; } message IpLinkSetResponse {} diff --git a/Sources/Containerization/VirtualMachineAgent.swift b/Sources/Containerization/VirtualMachineAgent.swift index c07bb83b..67ab8684 100644 --- a/Sources/Containerization/VirtualMachineAgent.swift +++ b/Sources/Containerization/VirtualMachineAgent.swift @@ -53,7 +53,7 @@ public protocol VirtualMachineAgent: Sendable { func deleteProcess(id: String, containerID: String?) async throws // Networking - func up(name: String) async throws + func up(name: String, mtu: UInt32?) async throws func down(name: String) async throws func addressAdd(name: String, address: String) async throws func routeAddDefault(name: String, gateway: String) async throws diff --git a/Sources/ContainerizationNetlink/NetlinkSession.swift b/Sources/ContainerizationNetlink/NetlinkSession.swift index b9f20262..98f78590 100644 --- a/Sources/ContainerizationNetlink/NetlinkSession.swift +++ b/Sources/ContainerizationNetlink/NetlinkSession.swift @@ -67,12 +67,17 @@ public struct NetlinkSession { /// - Parameters: /// - interface: The name of the interface. /// - up: The value to set the interface state to. - public func linkSet(interface: String, up: Bool) throws { + public func linkSet(interface: String, up: Bool, mtu: UInt32? = nil) throws { // ip link set dev [interface] [up|down] let interfaceIndex = try getInterfaceIndex(interface) - let mtuAttr = RTAttribute( - len: UInt16(RTAttribute.size + MemoryLayout.size), type: LinkAttributeType.IFLA_MTU) - let requestSize = NetlinkMessageHeader.size + InterfaceInfo.size + mtuAttr.paddedLen + // build the attribute only when mtu is supplied + let attr: RTAttribute? = + (mtu != nil) + ? RTAttribute( + len: UInt16(RTAttribute.size + MemoryLayout.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 @@ -91,16 +96,16 @@ public struct NetlinkSession { change: InterfaceFlags.DEFAULT_CHANGE) requestOffset = try requestInfo.appendBuffer(&requestBuffer, offset: requestOffset) - requestOffset = try mtuAttr.appendBuffer(&requestBuffer, offset: requestOffset) - guard - let newRequestOffset = requestBuffer.copyIn( - as: UInt32.self, - value: Self.mtu, - offset: requestOffset) - else { - throw NetlinkDataError.sendMarshalFailure + 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 NetlinkDataError.sendMarshalFailure + } + requestOffset = newRequestOffset } - requestOffset = newRequestOffset guard requestOffset == requestSize else { throw Error.unexpectedOffset(offset: requestOffset, size: requestSize) diff --git a/Tests/ContainerizationNetlinkTests/NetlinkSessionTest.swift b/Tests/ContainerizationNetlinkTests/NetlinkSessionTest.swift index 134a4818..174defdb 100644 --- a/Tests/ContainerizationNetlinkTests/NetlinkSessionTest.swift +++ b/Tests/ContainerizationNetlinkTests/NetlinkSessionTest.swift @@ -26,23 +26,29 @@ struct NetlinkSessionTest { // Lookup interface by name, truncated response with no attributes (not needed at present). let expectedLookupRequest = - "3400000012000100000000000cc00cc0110000000000000001000000ffffffff08001d00090000000c0003006574683000000000" - mockSocket.responses.append([ - 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x43, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - ]) + "3400000012000100000000000cc00cc0" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d00090000000c0003006574683000000000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“eth0”) + mockSocket.responses.append( + [UInt8]( + hex: + "2000000010000000000000000cc00cc0" // Netlink header (16 B) + + "00000100020000004310010000000000" // struct ifinfomsg (16 B) – no RT attrs + ) + ) - // Network down for interface. - let expectedDownRequest = "2800000010000500000000000cc00cc0110000000200000000000000ffffffff0800040000050000" - mockSocket.responses.append([ - 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, - ]) + // Link‑down request – 32‑byte payload, no attributes. + let expectedDownRequest = + "2000000010000500000000000cc00cc0" // Netlink header (16 B) + + "110000000200000000000000ffffffff" // struct ifinfomsg (16 B) – no RT attrs + mockSocket.responses.append( + [UInt8]( + hex: + "2400000002000001000000000cc00cc0" // Netlink header (16 B) + + "00000000200000001000050000000000" // nlmsg_err payload (16 B) + + "0c000000" // first 4 B of echoed header + ) + ) let session = NetlinkSession(socket: mockSocket) try session.linkSet(interface: "eth0", up: false) @@ -61,26 +67,74 @@ struct NetlinkSessionTest { // Lookup interface by name, truncated response with no attributes (not needed at present). let expectedLookupRequest = - "340000001200010000000000c00cc00c110000000000000001000000ffffffff08001d00090000000c0003006574683000000000" - mockSocket.responses.append([ - 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0xc0, 0x0c, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x43, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - ]) + "340000001200010000000000c00cc00c" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d00090000000c0003006574683000000000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“eth0”) + mockSocket.responses.append( + [UInt8]( + hex: + "200000001000000000000000c00cc00c" // Netlink header (16 B) + + "00000100020000004310010000000000" // struct ifinfomsg (16 B) – no attributes + ) + ) // Network up for interface. - let expectedUpRequest = "280000001000050000000000c00cc00c110000000200000001000000ffffffff0800040000050000" - mockSocket.responses.append([ - 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0c, 0xc0, 0x0c, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, - ]) + let expectedUpRequest = + "280000001000050000000000c00cc00c" // Netlink header (16 B) + + "110000000200000001000000ffffffff" // struct ifinfomsg (16 B) + + "0800040000050000" // RT attr: IFLA_MTU = 1280 (8 B) + mockSocket.responses.append( + [UInt8]( + hex: + "240000000200000100000000c00cc00c" // Netlink header (16 B) + + "00000000200000001000050000000000" // nlmsg_err payload (16 B) + + "11000000" // 1st 4 B of echoed offending header + ) + ) + + let session = NetlinkSession(socket: mockSocket) + try session.linkSet(interface: "eth0", up: true, mtu: 1280) + + #expect(mockSocket.requests.count == 2) + #expect(mockSocket.responseIndex == 2) + mockSocket.requests[0][8..<12] = [0, 0, 0, 0] + #expect(expectedLookupRequest == mockSocket.requests[0].hexEncodedString()) + mockSocket.requests[1][8..<12] = [0, 0, 0, 0] + #expect(expectedUpRequest == mockSocket.requests[1].hexEncodedString()) + } + + @Test func testNetworkLinkUpLoopback() throws { + let mockSocket = try MockNetlinkSocket() + mockSocket.pid = 0xc00c_c00c + + // Lookup loopback interface + let expectedLookupRequest = + "3000000012000100000000000cc00cc0" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d0009000000080003006c6f0000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“lo”) + mockSocket.responses.append( + [UInt8]( + hex: + "2000000010000000000000000cc00cc0" // Netlink header (16 B) + + "00000100010000004310010000000000" // struct ifinfomsg (16 B) – no attributes + ) + ) + + // Link up request for loopback, 32‑byte payload and no attributes + let expectedUpRequest = + "2000000010000500000000000cc00cc0" // Netlink header (16 B) + + "110000000100000001000000ffffffff" // struct ifinfomsg (16 B) – no RT attrs + mockSocket.responses.append( + [UInt8]( + hex: + "2400000002000001000000000cc00cc0" // Netlink header (16 B) + + "00000000200000001000050000000000" // nlmsg_err payload (16 B) + + "0c000000" // first 4 B of echoed offending header + ) + ) let session = NetlinkSession(socket: mockSocket) - try session.linkSet(interface: "eth0", up: true) + try session.linkSet(interface: "lo", up: true) #expect(mockSocket.requests.count == 2) #expect(mockSocket.responseIndex == 2) @@ -96,17 +150,19 @@ struct NetlinkSessionTest { // Lookup interface by name, truncated response with three attributes. let expectedLookupRequest = - "34000000120001000000000078563412110000000000000001000000ffffffff08001d00090000000c0003006574683000000000" - mockSocket.responses.append([ - 0x3c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x43, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x03, 0x00, 0x65, 0x74, 0x68, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d, 0x00, - 0xe8, 0x03, 0x00, 0x00, 0x05, 0x00, 0x10, 0x00, - 0x06, 0x00, 0x00, 0x00, - ]) + "34000000120001000000000078563412" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d00090000000c0003006574683000000000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“eth0”) + mockSocket.responses.append( + [UInt8]( + hex: + "3c000000100000000000000078563412" // Netlink header (16 B) + + "00000100020000004300010000000000" // struct ifinfomsg (16 B) + + "090003006574683000000000" // IFLA_IFNAME (“eth0”) attr (12 B) + + "08000d00e8030000" // IFLA_MTU = 1000 attr (8 B) + + "0500100006000000" // attr type 0x0010 (8 B) + ) + ) let session = NetlinkSession(socket: mockSocket) let links = try session.linkGet(interface: "eth0") @@ -135,27 +191,33 @@ struct NetlinkSessionTest { mockSocket.pid = 0x8765_4321 // Lookup all interfaces, responses with only the interface name attribute. - let expectedLookupRequest = "28000000120001030000000021436587110000000000000001000000ffffffff08001d0009000000" - mockSocket.responses.append([ - 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, - 0x00, 0x00, 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, - 0x49, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x03, 0x00, 0x6c, 0x6f, 0x00, 0x00, - ]) - mockSocket.responses.append([ - 0x2c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x03, 0x00, 0x74, 0x75, 0x6e, 0x6c, - 0x30, 0x00, 0x00, 0x00, - ]) - mockSocket.responses.append([ - 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, - ]) + let expectedLookupRequest = + "28000000120001030000000021436587" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d0009000000" // RT attr: IFLA_EXT_MASK (8 B) + mockSocket.responses.append( + [UInt8]( + hex: + "28000000100002000000000021436587" // Netlink header (16 B) + + "00000403010000004900010000000000" // struct ifinfomsg (16 B) + + "070003006c6f0000" // IFLA_IFNAME “lo” (8 B, padded) + ) + ) + mockSocket.responses.append( + [UInt8]( + hex: + "2c000000100002000000000021436587" // Netlink header (16 B) + + "00000003040000008000000000000000" // struct ifinfomsg (16 B) + + "0a00030074756e6c30000000" // IFLA_IFNAME “tunl0” attr (12 B, padded) + ) + ) + mockSocket.responses.append( + [UInt8]( + hex: + "14000000030002000000000021436587" // Netlink header (16 B) – NLMSG_DONE + + "00000000" // 4-byte payload + ) + ) let session = NetlinkSession(socket: mockSocket) let links = try session.linkGet() @@ -185,23 +247,31 @@ struct NetlinkSessionTest { // Lookup interface by name, truncated response with no attributes (not needed at present). let expectedLookupRequest = - "3400000012000100000000000cc00cc0110000000000000001000000ffffffff08001d00090000000c0003006574683000000000" - mockSocket.responses.append([ - 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x43, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - ]) + "3400000012000100000000000cc00cc0" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d00090000000c0003006574683000000000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“eth0”) + mockSocket.responses.append( + [UInt8]( + hex: + "2000000010000000000000000cc00cc0" // Netlink header (16 B) + + "00000100020000004310010000000000" // struct ifinfomsg (16 B) – no attributes + ) + ) // Network down for interface. - let expectedAddRequest = "2800000014000506000000000cc00cc0021800000200000008000200c0a840fa08000100c0a840fa" - mockSocket.responses.append([ - 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x1f, 0x00, 0x00, 0x00, - ]) + let expectedAddRequest = + "2800000014000506000000000cc00cc0" // Netlink header (16 B) + + "0218000002000000" // ifaddrmsg (8 B): AF_INET, /24, ifindex 2 + + "08000200c0a840fa" // RT attr: IFA_LOCAL 192.168.64.250 + + "08000100c0a840fa" // RT attr: IFA_ADDRESS 192.168.64.250 + mockSocket.responses.append( + [UInt8]( + hex: + "2400000002000001000000000cc00cc0" // Netlink header (16 B) + + "00000000280000001400050600000000" // nlmsg_err payload (16 B) + + "1f000000" // first 4 B of echoed offending header + ) + ) let session = NetlinkSession(socket: mockSocket) try session.addressAdd(interface: "eth0", address: "192.168.64.250/24") @@ -219,24 +289,34 @@ struct NetlinkSessionTest { // Lookup interface by name, truncated response with no attributes (not needed at present). let expectedLookupRequest = - "3400000012000100000000000cc00cc0110000000000000001000000ffffffff08001d00090000000c0003006574683000000000" - mockSocket.responses.append([ - 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x43, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - ]) + "3400000012000100000000000cc00cc0" // Netlink header (16 B) + + "110000000000000001000000ffffffff" // struct ifinfomsg (16 B) + + "08001d00090000000c0003006574683000000000" // RT attrs: IFLA_EXT_MASK + IFLA_IFNAME (“eth0”) + mockSocket.responses.append( + [UInt8]( + hex: + "2000000010000000000000000cc00cc0" // Netlink header (16 B) + + "00000100020000004310010000000000" // struct ifinfomsg (16 B) – no attributes + ) + ) // Add link route. let expectedAddRequest = - "3400000018000506000000000cc00cc002180000fe02fd010000000008000100c0a8400008000700c0a840030800040002000000" - mockSocket.responses.append([ - 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x0c, 0xc0, - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x1f, 0x00, 0x00, 0x00, - ]) + "3400000018000506000000000cc00cc0" // Netlink header (16 B) + + "02180000fe02fd0100000000" // struct rtmsg (12 B): AF_INET, dst/24, + // table=RT_TABLE_MAIN (0xfe), proto=RTPROT_BOOT (0x02), + // scope=RT_SCOPE_UNIVERSE (0xfd), type=RTN_UNICAST (0x01) + + "08000100c0a84000" // RTA_DST 192.168.64.0 + + "08000700c0a84003" // RTA_PREFSRC 192.168.64.3 + + "0800040002000000" // RTA_OIF ifindex 2 (eth0) + mockSocket.responses.append( + [UInt8]( + hex: + "2400000002000001000000000cc00cc0" // Netlink header (16 B) + + "00000000280000001400050600000000" // nlmsg_err payload (16 B) + + "1f000000" // first 4 B of echoed offending header + ) + ) let session = NetlinkSession(socket: mockSocket) try session.routeAdd( @@ -253,3 +333,14 @@ struct NetlinkSessionTest { #expect(expectedAddRequest == mockSocket.requests[1].hexEncodedString()) } } + +extension Array where Element == UInt8 { + /// Initializes `[UInt8]` from an even-length hex string + init(hex: String) { + self = stride(from: 0, to: hex.count, by: 2).compactMap { + UInt8( + hex[hex.index(hex.startIndex, offsetBy: $0)...] + .prefix(2), radix: 16) + } + } +} diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index ba99ec80..fc90a0cd 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -622,7 +622,8 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid do { let socket = try DefaultNetlinkSocket() let session = NetlinkSession(socket: socket, log: log) - try session.linkSet(interface: request.interface, up: request.up) + let mtuValue: UInt32? = request.hasMtu ? request.mtu : nil + try session.linkSet(interface: request.interface, up: request.up, mtu: mtuValue) } catch { log.error( "ip-link-set",