From 40abf11b6a891e7de2a5c35fdb22c36f5c727cfb Mon Sep 17 00:00:00 2001 From: Srinivas Jagannathan Date: Fri, 6 Feb 2026 14:50:14 -0800 Subject: [PATCH] Add IPv6 gateway support for container networking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ipv6Gateway field to network attachments and status to enable proper IPv6 routing in containers. This complements the existing ipv6Address/ipv6Subnet fields. Changes: - Attachment: Add ipv6Gateway field - NetworkState: Add ipv6Gateway to NetworkStatus - IsolatedInterfaceStrategy: Pass IPv6 gateway to NATInterface - NonisolatedInterfaceStrategy: Pass IPv6 gateway to NATNetworkInterface - NetworkService: Include ipv6Gateway in attachment creation - ReservedVmnetNetwork: Calculate and store ipv6Gateway Depends on: apple/containerization IPv6 support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Sources/ContainerResource/Network/Attachment.swift | 5 +++++ Sources/ContainerResource/Network/NetworkState.swift | 6 ++++++ .../Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift | 3 +++ .../Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift | 3 +++ .../ContainerNetworkService/Server/NetworkService.swift | 3 +++ .../Server/ReservedVmnetNetwork.swift | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/Sources/ContainerResource/Network/Attachment.swift b/Sources/ContainerResource/Network/Attachment.swift index f7178f2d9..8879bd5da 100644 --- a/Sources/ContainerResource/Network/Attachment.swift +++ b/Sources/ContainerResource/Network/Attachment.swift @@ -29,6 +29,9 @@ public struct Attachment: Codable, Sendable { /// The CIDR address describing the interface IPv6 address, with the prefix length of the subnet. /// The address is nil if the IPv6 subnet could not be determined at network creation time. public let ipv6Address: CIDRv6? + /// The IPv6 gateway address. + /// The value is nil if the IPv6 subnet could not be determined at network creation time. + public let ipv6Gateway: IPv6Address? /// The MAC address associated with the attachment (optional). public let macAddress: MACAddress? @@ -38,6 +41,7 @@ public struct Attachment: Codable, Sendable { ipv4Address: CIDRv4, ipv4Gateway: IPv4Address, ipv6Address: CIDRv6?, + ipv6Gateway: IPv6Address? = nil, macAddress: MACAddress? ) { self.network = network @@ -45,6 +49,7 @@ public struct Attachment: Codable, Sendable { self.ipv4Address = ipv4Address self.ipv4Gateway = ipv4Gateway self.ipv6Address = ipv6Address + self.ipv6Gateway = ipv6Gateway self.macAddress = macAddress } } diff --git a/Sources/ContainerResource/Network/NetworkState.swift b/Sources/ContainerResource/Network/NetworkState.swift index 3430d761d..2c80b3f84 100644 --- a/Sources/ContainerResource/Network/NetworkState.swift +++ b/Sources/ContainerResource/Network/NetworkState.swift @@ -30,14 +30,20 @@ public struct NetworkStatus: Codable, Sendable { /// The value is nil if the IPv6 subnet cannot be determined at creation time. public let ipv6Subnet: CIDRv6? + /// The gateway IPv6 address. + /// The value is nil if the IPv6 subnet cannot be determined at creation time. + public let ipv6Gateway: IPv6Address? + public init( ipv4Subnet: CIDRv4, ipv4Gateway: IPv4Address, ipv6Subnet: CIDRv6?, + ipv6Gateway: IPv6Address? = nil ) { self.ipv4Subnet = ipv4Subnet self.ipv4Gateway = ipv4Gateway self.ipv6Subnet = ipv6Subnet + self.ipv6Gateway = ipv6Gateway } } diff --git a/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift b/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift index 451cb2ac4..5c2d9666b 100644 --- a/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift +++ b/Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift @@ -25,9 +25,12 @@ import Containerization struct IsolatedInterfaceStrategy: InterfaceStrategy { public func toInterface(attachment: Attachment, interfaceIndex: Int, additionalData: XPCMessage?) -> Interface { let ipv4Gateway = interfaceIndex == 0 ? attachment.ipv4Gateway : nil + let ipv6Gateway = interfaceIndex == 0 ? attachment.ipv6Gateway : nil return NATInterface( ipv4Address: attachment.ipv4Address, ipv4Gateway: ipv4Gateway, + ipv6Address: attachment.ipv6Address, + ipv6Gateway: ipv6Gateway, macAddress: attachment.macAddress, // https://github.com/apple/containerization/pull/38 mtu: 1280 diff --git a/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift b/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift index 5f0f1e200..c9b918c87 100644 --- a/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift +++ b/Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift @@ -44,9 +44,12 @@ struct NonisolatedInterfaceStrategy: InterfaceStrategy { log.info("creating NATNetworkInterface with network reference") let ipv4Gateway = interfaceIndex == 0 ? attachment.ipv4Gateway : nil + let ipv6Gateway = interfaceIndex == 0 ? attachment.ipv6Gateway : nil return NATNetworkInterface( ipv4Address: attachment.ipv4Address, ipv4Gateway: ipv4Gateway, + ipv6Address: attachment.ipv6Address, + ipv6Gateway: ipv6Gateway, reference: networkRef, macAddress: attachment.macAddress, // https://github.com/apple/containerization/pull/38 diff --git a/Sources/Services/ContainerNetworkService/Server/NetworkService.swift b/Sources/Services/ContainerNetworkService/Server/NetworkService.swift index f40bdf772..54d113852 100644 --- a/Sources/Services/ContainerNetworkService/Server/NetworkService.swift +++ b/Sources/Services/ContainerNetworkService/Server/NetworkService.swift @@ -77,6 +77,7 @@ public actor NetworkService: Sendable { ipv4Address: try CIDRv4(ip, prefix: status.ipv4Subnet.prefix), ipv4Gateway: status.ipv4Gateway, ipv6Address: ipv6Address, + ipv6Gateway: status.ipv6Gateway, macAddress: macAddress ) log?.info( @@ -86,6 +87,7 @@ public actor NetworkService: Sendable { "ipv4Address": "\(attachment.ipv4Address)", "ipv4Gateway": "\(attachment.ipv4Gateway)", "ipv6Address": "\(attachment.ipv6Address?.description ?? "unavailable")", + "ipv6Gateway": "\(attachment.ipv6Gateway?.description ?? "unavailable")", "macAddress": "\(attachment.macAddress?.description ?? "unspecified")", ]) let reply = message.reply() @@ -136,6 +138,7 @@ public actor NetworkService: Sendable { ipv4Address: ipv4Address, ipv4Gateway: status.ipv4Gateway, ipv6Address: ipv6Address, + ipv6Gateway: status.ipv6Gateway, macAddress: macAddress ) log?.debug( diff --git a/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift b/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift index f1c747301..5e37be383 100644 --- a/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift +++ b/Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift @@ -41,6 +41,7 @@ public final class ReservedVmnetNetwork: Network { let ipv4Subnet: CIDRv4 let ipv4Gateway: IPv4Address let ipv6Subnet: CIDRv6 + let ipv6Gateway: IPv6Address } private let stateMutex: Mutex @@ -85,6 +86,7 @@ public final class ReservedVmnetNetwork: Network { ipv4Subnet: networkInfo.ipv4Subnet, ipv4Gateway: networkInfo.ipv4Gateway, ipv6Subnet: networkInfo.ipv6Subnet, + ipv6Gateway: networkInfo.ipv6Gateway ) state.networkState = NetworkState.running(configuration, networkStatus) state.network = networkInfo.network @@ -183,6 +185,7 @@ public final class ReservedVmnetNetwork: Network { } let prefixIpv6Addr = try IPv6Address(prefixIpv6Bytes) let runningV6Subnet = try CIDRv6(prefixIpv6Addr, prefix: prefix) + let runningV6Gateway = IPv6Address(runningV6Subnet.lower.value + 1) log.info( "started vmnet network", @@ -199,6 +202,7 @@ public final class ReservedVmnetNetwork: Network { ipv4Subnet: runningSubnet, ipv4Gateway: runningGateway, ipv6Subnet: runningV6Subnet, + ipv6Gateway: runningV6Gateway ) } }