diff --git a/Sources/ContainerResource/Network/Attachment.swift b/Sources/ContainerResource/Network/Attachment.swift index 96cee934..ada29d9f 100644 --- a/Sources/ContainerResource/Network/Attachment.swift +++ b/Sources/ContainerResource/Network/Attachment.swift @@ -31,6 +31,8 @@ public struct Attachment: Codable, Sendable { public let ipv6Address: CIDRv6? /// The MAC address associated with the attachment (optional). public let macAddress: MACAddress? + /// The MTU for the network interface. + public let mtu: UInt32? public init( network: String, @@ -38,7 +40,8 @@ public struct Attachment: Codable, Sendable { ipv4Address: CIDRv4, ipv4Gateway: IPv4Address, ipv6Address: CIDRv6?, - macAddress: MACAddress? + macAddress: MACAddress?, + mtu: UInt32? = nil ) { self.network = network self.hostname = hostname @@ -46,5 +49,6 @@ public struct Attachment: Codable, Sendable { self.ipv4Gateway = ipv4Gateway self.ipv6Address = ipv6Address self.macAddress = macAddress + self.mtu = mtu } } diff --git a/Sources/ContainerResource/Network/AttachmentConfiguration.swift b/Sources/ContainerResource/Network/AttachmentConfiguration.swift index 7deb9e16..d7e3dff6 100644 --- a/Sources/ContainerResource/Network/AttachmentConfiguration.swift +++ b/Sources/ContainerResource/Network/AttachmentConfiguration.swift @@ -38,8 +38,12 @@ public struct AttachmentOptions: Codable, Sendable { /// The MAC address associated with the attachment (optional). public let macAddress: MACAddress? - public init(hostname: String, macAddress: MACAddress? = nil) { + /// The MTU for the network interface. + public let mtu: UInt32? + + public init(hostname: String, macAddress: MACAddress? = nil, mtu: UInt32? = nil) { self.hostname = hostname self.macAddress = macAddress + self.mtu = mtu } } diff --git a/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift b/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift index 451cb2ac..5a960902 100644 --- a/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift +++ b/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift @@ -30,7 +30,7 @@ struct IsolatedInterfaceStrategy: InterfaceStrategy { ipv4Gateway: ipv4Gateway, macAddress: attachment.macAddress, // https://github.com/apple/containerization/pull/38 - mtu: 1280 + mtu: attachment.mtu ?? 1280 ) } } diff --git a/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift b/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift index 5f0f1e20..a61a3237 100644 --- a/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift +++ b/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift @@ -50,7 +50,7 @@ struct NonisolatedInterfaceStrategy: InterfaceStrategy { reference: networkRef, macAddress: attachment.macAddress, // https://github.com/apple/containerization/pull/38 - mtu: 1280 + mtu: attachment.mtu ?? 1280 ) } } diff --git a/Sources/Services/ContainerAPIService/Client/Flags.swift b/Sources/Services/ContainerAPIService/Client/Flags.swift index b83d7820..3d2185ec 100644 --- a/Sources/Services/ContainerAPIService/Client/Flags.swift +++ b/Sources/Services/ContainerAPIService/Client/Flags.swift @@ -268,7 +268,7 @@ public struct Flags { @Option(name: .long, help: "Use the specified name as the container ID") public var name: String? - @Option(name: [.customLong("network")], help: "Attach the container to a network (format: [,mac=XX:XX:XX:XX:XX:XX])") + @Option(name: [.customLong("network")], help: "Attach the container to a network (format: [,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE])") public var networks: [String] = [] @Flag(name: [.customLong("no-dns")], help: "Do not configure DNS in the container") diff --git a/Sources/Services/ContainerAPIService/Client/Parser.swift b/Sources/Services/ContainerAPIService/Client/Parser.swift index a701edc9..c9d64701 100644 --- a/Sources/Services/ContainerAPIService/Client/Parser.swift +++ b/Sources/Services/ContainerAPIService/Client/Parser.swift @@ -795,16 +795,18 @@ public struct Parser { public struct ParsedNetwork { public let name: String public let macAddress: String? + public let mtu: UInt32? - public init(name: String, macAddress: String? = nil) { + public init(name: String, macAddress: String? = nil, mtu: UInt32? = nil) { self.name = name self.macAddress = macAddress + self.mtu = mtu } } /// Parse network attachment with optional properties - /// Format: network_name[,mac=XX:XX:XX:XX:XX:XX] - /// Example: "backend,mac=02:42:ac:11:00:02" + /// Format: network_name[,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE] + /// Example: "backend,mac=02:42:ac:11:00:02,mtu=1500" public static func network(_ networkSpec: String) throws -> ParsedNetwork { guard !networkSpec.isEmpty else { throw ContainerizationError(.invalidArgument, message: "network specification cannot be empty") @@ -822,6 +824,7 @@ public struct Parser { } var macAddress: String? + var mtu: UInt32? // Parse properties if any for part in parts.dropFirst() { @@ -848,15 +851,23 @@ public struct Parser { ) } macAddress = value + case "mtu": + guard let mtuValue = UInt32(value), mtuValue >= 1280, mtuValue <= 65535 else { + throw ContainerizationError( + .invalidArgument, + message: "invalid mtu value '\(value)': must be between 1280 and 65535" + ) + } + mtu = mtuValue default: throw ContainerizationError( .invalidArgument, - message: "unknown network property '\(key)'. Available properties: mac" + message: "unknown network property '\(key)'. Available properties: mac, mtu" ) } } - return ParsedNetwork(name: networkName, macAddress: macAddress) + return ParsedNetwork(name: networkName, macAddress: macAddress, mtu: mtu) } // MARK: DNS diff --git a/Sources/Services/ContainerAPIService/Client/Utility.swift b/Sources/Services/ContainerAPIService/Client/Utility.swift index 0e68d5e8..df0c6f28 100644 --- a/Sources/Services/ContainerAPIService/Client/Utility.swift +++ b/Sources/Services/ContainerAPIService/Client/Utility.swift @@ -297,15 +297,16 @@ public struct Utility { // attach the first network using the fqdn, and the rest using just the container ID return try networks.enumerated().map { item in let macAddress = try item.element.macAddress.map { try MACAddress($0) } + let mtu = item.element.mtu ?? 1280 guard item.offset == 0 else { return AttachmentConfiguration( network: item.element.name, - options: AttachmentOptions(hostname: containerId, macAddress: macAddress) + options: AttachmentOptions(hostname: containerId, macAddress: macAddress, mtu: mtu) ) } return AttachmentConfiguration( network: item.element.name, - options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: macAddress) + options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: macAddress, mtu: mtu) ) } } @@ -314,7 +315,7 @@ public struct Utility { guard let builtinNetworkId else { throw ContainerizationError(.invalidState, message: "builtin network is not present") } - return [AttachmentConfiguration(network: builtinNetworkId, options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: nil))] + return [AttachmentConfiguration(network: builtinNetworkId, options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: nil, mtu: 1280))] } private static func getKernel(management: Flags.Management) async throws -> Kernel { diff --git a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift index 6e99c5e2..3c824777 100644 --- a/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift +++ b/Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift @@ -417,9 +417,26 @@ public actor ContainersService { hostname: n.options.hostname, macAddress: n.options.macAddress ) - guard let allocatedAttach = allocatedAttach else { + guard var allocatedAttach = allocatedAttach else { throw ContainerizationError(.internalError, message: "failed to allocate a network") } + + if let mtu = n.options.mtu { + let a = allocatedAttach.attachment + allocatedAttach = AllocatedAttachment( + attachment: Attachment( + network: a.network, + hostname: a.hostname, + ipv4Address: a.ipv4Address, + ipv4Gateway: a.ipv4Gateway, + ipv6Address: a.ipv6Address, + macAddress: a.macAddress, + mtu: mtu + ), + additionalData: allocatedAttach.additionalData, + pluginInfo: allocatedAttach.pluginInfo + ) + } allocatedAttachments.append(allocatedAttach) } diff --git a/Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift b/Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift index 89fbc87b..7c2c59c5 100644 --- a/Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift +++ b/Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift @@ -191,6 +191,19 @@ class TestCLINetwork: CLITest { } } + @Test func testNetworkMTU() async throws { + let name = getLowercasedTestName() + try? doStop(name: name) + try? doRemove(name: name) + + try doLongRun(name: name, args: ["--network", "default,mtu=1500"]) + defer { try? doStop(name: name) } + + try waitForContainerRunning(name) + let output = try doExec(name: name, cmd: ["ip", "link", "show", "eth0"]) + #expect(output.contains("mtu 1500"), "expected mtu 1500 in ip link output: \(output)") + } + @available(macOS 26, *) @Test func testIsolatedNetwork() async throws { do {