diff --git a/Package.resolved b/Package.resolved index 25c0dcb3..b187c8e4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "8b51a9ec068537ab57ce9b8034b5b84a02a4697e4a6be491954e5fbda7e5783b", + "originHash" : "a16d47b381f0ceab43ada8881a07031931470e91ccc7007c5756be1d8330d9ec", "pins" : [ { "identity" : "async-http-client", "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", - "version" : "1.26.1" + "revision" : "4b99975677236d13f0754339864e5360142ff5a1", + "version" : "1.30.3" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "a56a157218877ef3e9625f7e1f7b2cb7e46ead1b", - "version" : "1.26.1" + "revision" : "8f57f68b9d247fe3759fa9f18e1fe919911e6031", + "version" : "1.27.1" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b", - "version" : "1.5.1" + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" } }, { @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", - "version" : "1.3.2" + "revision" : "810496cf121e525d660cd0ea89a758740476b85f", + "version" : "1.5.1" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms.git", "state" : { - "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", - "version" : "1.0.4" + "revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804", + "version" : "1.1.1" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-certificates.git", "state" : { - "revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585", - "version" : "1.10.0" + "revision" : "7d5f6124c91a2d06fb63a811695a3400d15a100e", + "version" : "1.17.1" } }, { @@ -78,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" } }, { @@ -87,8 +87,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", - "version" : "3.12.3" + "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", + "version" : "3.15.1" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", + "version" : "1.3.1" } }, { @@ -96,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-docc-plugin", "state" : { - "revision" : "d1691545d53581400b1de9b0472d45eb25c19fed", - "version" : "1.4.4" + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" } }, { @@ -114,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-structured-headers.git", "state" : { - "revision" : "db6eea3692638a65e2124990155cd220c2915903", - "version" : "1.3.0" + "revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b", + "version" : "1.6.0" } }, { @@ -123,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types.git", "state" : { - "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", - "version" : "1.4.0" + "revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca", + "version" : "1.5.1" } }, { @@ -132,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", - "version" : "1.6.3" + "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", + "version" : "1.9.1" } }, { @@ -141,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", - "version" : "2.83.0" + "revision" : "5e72fc102906ebe75a3487595a653e6f43725552", + "version" : "2.94.0" } }, { @@ -150,8 +159,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "145db1962f4f33a4ea07a32e751d5217602eea29", - "version" : "1.28.0" + "revision" : "3df009d563dc9f21a5c85b33d8c2e34d2e4f8c3b", + "version" : "1.32.1" } }, { @@ -159,8 +168,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", - "version" : "1.36.0" + "revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80", + "version" : "1.39.0" } }, { @@ -177,8 +186,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", - "version" : "1.24.0" + "revision" : "60c3e187154421171721c1a38e800b390680fb5d", + "version" : "1.26.0" } }, { @@ -186,8 +195,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-numerics.git", "state" : { - "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version" : "1.0.3" + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" } }, { @@ -195,8 +204,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "102a647b573f60f73afdce5613a51d71349fe507", - "version" : "1.30.0" + "revision" : "c5ab62237f21cad094812719a1bbe29443407c5f", + "version" : "1.34.1" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" } }, { @@ -204,8 +222,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/swift-service-lifecycle.git", "state" : { - "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", - "version" : "2.8.0" + "revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348", + "version" : "2.9.1" } }, { @@ -213,8 +231,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", - "version" : "1.6.3" + "revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df", + "version" : "1.6.4" } }, { @@ -228,4 +246,4 @@ } ], "version" : 3 -} +} \ No newline at end of file diff --git a/Package.swift b/Package.swift index d3e1c7db..5884dabc 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ let package = Package( .library(name: "ContainerizationEXT4", targets: ["ContainerizationEXT4"]), .library(name: "ContainerizationOCI", targets: ["ContainerizationOCI"]), .library(name: "ContainerizationNetlink", targets: ["ContainerizationNetlink"]), + .library(name: "ContainerizationICMP", targets: ["ContainerizationICMP"]), .library(name: "ContainerizationIO", targets: ["ContainerizationIO"]), .library(name: "ContainerizationOS", targets: ["ContainerizationOS"]), .library(name: "ContainerizationExtras", targets: ["ContainerizationExtras"]), @@ -196,6 +197,20 @@ let package = Package( .product(name: "Crypto", package: "swift-crypto"), ] ), + .target( + name: "ContainerizationICMP", + dependencies: [ + .product(name: "Logging", package: "swift-log"), + "ContainerizationExtras", + ] + ), + .testTarget( + name: "ContainerizationICMPTests", + dependencies: [ + "ContainerizationExtras", + "ContainerizationICMP", + ] + ), .target( name: "ContainerizationNetlink", dependencies: [ diff --git a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift index 7b2fcaf1..f1977265 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -98,29 +98,29 @@ public struct Com_Apple_Containerization_Sandbox_V3_Stdio: Sendable { // methods supported on all messages. public var stdinPort: Int32 { - get {return _stdinPort ?? 0} + get {_stdinPort ?? 0} set {_stdinPort = newValue} } /// Returns true if `stdinPort` has been explicitly set. - public var hasStdinPort: Bool {return self._stdinPort != nil} + public var hasStdinPort: Bool {self._stdinPort != nil} /// Clears the value of `stdinPort`. Subsequent reads from it will return its default value. public mutating func clearStdinPort() {self._stdinPort = nil} public var stdoutPort: Int32 { - get {return _stdoutPort ?? 0} + get {_stdoutPort ?? 0} set {_stdoutPort = newValue} } /// Returns true if `stdoutPort` has been explicitly set. - public var hasStdoutPort: Bool {return self._stdoutPort != nil} + public var hasStdoutPort: Bool {self._stdoutPort != nil} /// Clears the value of `stdoutPort`. Subsequent reads from it will return its default value. public mutating func clearStdoutPort() {self._stdoutPort = nil} public var stderrPort: Int32 { - get {return _stderrPort ?? 0} + get {_stderrPort ?? 0} set {_stderrPort = newValue} } /// Returns true if `stderrPort` has been explicitly set. - public var hasStderrPort: Bool {return self._stderrPort != nil} + public var hasStderrPort: Bool {self._stderrPort != nil} /// Clears the value of `stderrPort`. Subsequent reads from it will return its default value. public mutating func clearStderrPort() {self._stderrPort = nil} @@ -225,11 +225,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest: Sendable public var guestPath: String = String() public var guestSocketPermissions: UInt32 { - get {return _guestSocketPermissions ?? 0} + get {_guestSocketPermissions ?? 0} set {_guestSocketPermissions = newValue} } /// Returns true if `guestSocketPermissions` has been explicitly set. - public var hasGuestSocketPermissions: Bool {return self._guestSocketPermissions != nil} + public var hasGuestSocketPermissions: Bool {self._guestSocketPermissions != nil} /// Clears the value of `guestSocketPermissions`. Subsequent reads from it will return its default value. public mutating func clearGuestSocketPermissions() {self._guestSocketPermissions = nil} @@ -368,11 +368,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_SetenvRequest: Sendable { public var key: String = String() public var value: String { - get {return _value ?? String()} + get {_value ?? String()} set {_value = newValue} } /// Returns true if `value` has been explicitly set. - public var hasValue: Bool {return self._value != nil} + public var hasValue: Bool {self._value != nil} /// Clears the value of `value`. Subsequent reads from it will return its default value. public mutating func clearValue() {self._value = nil} @@ -411,11 +411,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_GetenvResponse: Sendable { // methods supported on all messages. public var value: String { - get {return _value ?? String()} + get {_value ?? String()} set {_value = newValue} } /// Returns true if `value` has been explicitly set. - public var hasValue: Bool {return self._value != nil} + public var hasValue: Bool {self._value != nil} /// Clears the value of `value`. Subsequent reads from it will return its default value. public mutating func clearValue() {self._value = nil} @@ -426,7 +426,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_GetenvResponse: Sendable { fileprivate var _value: String? = nil } -public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unchecked Sendable { +public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -434,58 +434,58 @@ public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unche public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} public var stdin: UInt32 { - get {return _stdin ?? 0} + get {_stdin ?? 0} set {_stdin = newValue} } /// Returns true if `stdin` has been explicitly set. - public var hasStdin: Bool {return self._stdin != nil} + public var hasStdin: Bool {self._stdin != nil} /// Clears the value of `stdin`. Subsequent reads from it will return its default value. public mutating func clearStdin() {self._stdin = nil} public var stdout: UInt32 { - get {return _stdout ?? 0} + get {_stdout ?? 0} set {_stdout = newValue} } /// Returns true if `stdout` has been explicitly set. - public var hasStdout: Bool {return self._stdout != nil} + public var hasStdout: Bool {self._stdout != nil} /// Clears the value of `stdout`. Subsequent reads from it will return its default value. public mutating func clearStdout() {self._stdout = nil} public var stderr: UInt32 { - get {return _stderr ?? 0} + get {_stderr ?? 0} set {_stderr = newValue} } /// Returns true if `stderr` has been explicitly set. - public var hasStderr: Bool {return self._stderr != nil} + public var hasStderr: Bool {self._stderr != nil} /// Clears the value of `stderr`. Subsequent reads from it will return its default value. public mutating func clearStderr() {self._stderr = nil} public var ociRuntimePath: String { - get {return _ociRuntimePath ?? String()} + get {_ociRuntimePath ?? String()} set {_ociRuntimePath = newValue} } /// Returns true if `ociRuntimePath` has been explicitly set. - public var hasOciRuntimePath: Bool {return self._ociRuntimePath != nil} + public var hasOciRuntimePath: Bool {self._ociRuntimePath != nil} /// Clears the value of `ociRuntimePath`. Subsequent reads from it will return its default value. public mutating func clearOciRuntimePath() {self._ociRuntimePath = nil} public var configuration: Data = Data() public var options: Data { - get {return _options ?? Data()} + get {_options ?? Data()} set {_options = newValue} } /// Returns true if `options` has been explicitly set. - public var hasOptions: Bool {return self._options != nil} + public var hasOptions: Bool {self._options != nil} /// Clears the value of `options`. Subsequent reads from it will return its default value. public mutating func clearOptions() {self._options = nil} @@ -519,11 +519,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_WaitProcessRequest: Sendable public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -542,11 +542,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: Sendabl public var exitCode: Int32 = 0 public var exitedAt: SwiftProtobuf.Google_Protobuf_Timestamp { - get {return _exitedAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} + get {_exitedAt ?? SwiftProtobuf.Google_Protobuf_Timestamp()} set {_exitedAt = newValue} } /// Returns true if `exitedAt` has been explicitly set. - public var hasExitedAt: Bool {return self._exitedAt != nil} + public var hasExitedAt: Bool {self._exitedAt != nil} /// Clears the value of `exitedAt`. Subsequent reads from it will return its default value. public mutating func clearExitedAt() {self._exitedAt = nil} @@ -565,11 +565,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest: Sendab public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -602,11 +602,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_DeleteProcessRequest: Sendab public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -635,11 +635,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_StartProcessRequest: Sendabl public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -670,11 +670,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_KillProcessRequest: Sendable public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -707,11 +707,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest: Se public var id: String = String() public var containerID: String { - get {return _containerID ?? String()} + get {_containerID ?? String()} set {_containerID = newValue} } /// Returns true if `containerID` has been explicitly set. - public var hasContainerID: Bool {return self._containerID != nil} + public var hasContainerID: Bool {self._containerID != nil} /// Clears the value of `containerID`. Subsequent reads from it will return its default value. public mutating func clearContainerID() {self._containerID = nil} @@ -758,7 +758,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_MkdirResponse: Sendable { public init() {} } -public struct Com_Apple_Containerization_Sandbox_V3_WriteFileRequest: @unchecked Sendable { +public struct Com_Apple_Containerization_Sandbox_V3_WriteFileRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -770,11 +770,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_WriteFileRequest: @unchecked public var mode: UInt32 = 0 public var flags: Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.WriteFileFlags { - get {return _flags ?? Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.WriteFileFlags()} + get {_flags ?? Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.WriteFileFlags()} set {_flags = newValue} } /// Returns true if `flags` has been explicitly set. - public var hasFlags: Bool {return self._flags != nil} + public var hasFlags: Bool {self._flags != nil} /// Clears the value of `flags`. Subsequent reads from it will return its default value. public mutating func clearFlags() {self._flags = nil} @@ -811,7 +811,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_WriteFileResponse: Sendable public init() {} } -public struct Com_Apple_Containerization_Sandbox_V3_CopyInChunk: @unchecked Sendable { +public struct Com_Apple_Containerization_Sandbox_V3_CopyInChunk: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -838,7 +838,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyInChunk: @unchecked Send public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Content: Equatable, @unchecked Sendable { + public enum OneOf_Content: Equatable, Sendable { /// Initialization message (must be first). case init_p(Com_Apple_Containerization_Sandbox_V3_CopyInInit) /// File data chunk. @@ -891,7 +891,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: Sendable { public init() {} } -public struct Com_Apple_Containerization_Sandbox_V3_CopyOutChunk: @unchecked Sendable { +public struct Com_Apple_Containerization_Sandbox_V3_CopyOutChunk: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -918,7 +918,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyOutChunk: @unchecked Sen public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Content: Equatable, @unchecked Sendable { + public enum OneOf_Content: Equatable, Sendable { /// Initialization message with metadata (first chunk). case init_p(Com_Apple_Containerization_Sandbox_V3_CopyOutInit) /// File data chunk. @@ -952,11 +952,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: Sendable { public var up: Bool = false public var mtu: UInt32 { - get {return _mtu ?? 0} + get {_mtu ?? 0} set {_mtu = newValue} } /// Returns true if `mtu` has been explicitly set. - public var hasMtu: Bool {return self._mtu != nil} + public var hasMtu: Bool {self._mtu != nil} /// Clears the value of `mtu`. Subsequent reads from it will return its default value. public mutating func clearMtu() {self._mtu = nil} @@ -1061,11 +1061,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest: Sendabl public var nameservers: [String] = [] public var domain: String { - get {return _domain ?? String()} + get {_domain ?? String()} set {_domain = newValue} } /// Returns true if `domain` has been explicitly set. - public var hasDomain: Bool {return self._domain != nil} + public var hasDomain: Bool {self._domain != nil} /// Clears the value of `domain`. Subsequent reads from it will return its default value. public mutating func clearDomain() {self._domain = nil} @@ -1100,11 +1100,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: Senda public var entries: [Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry] = [] public var comment: String { - get {return _comment ?? String()} + get {_comment ?? String()} set {_comment = newValue} } /// Returns true if `comment` has been explicitly set. - public var hasComment: Bool {return self._comment != nil} + public var hasComment: Bool {self._comment != nil} /// Clears the value of `comment`. Subsequent reads from it will return its default value. public mutating func clearComment() {self._comment = nil} @@ -1120,11 +1120,11 @@ public struct Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: Senda public var hostnames: [String] = [] public var comment: String { - get {return _comment ?? String()} + get {_comment ?? String()} set {_comment = newValue} } /// Returns true if `comment` has been explicitly set. - public var hasComment: Bool {return self._comment != nil} + public var hasComment: Bool {self._comment != nil} /// Clears the value of `comment`. Subsequent reads from it will return its default value. public mutating func clearComment() {self._comment = nil} @@ -1230,57 +1230,57 @@ public struct Com_Apple_Containerization_Sandbox_V3_ContainerStats: @unchecked S // methods supported on all messages. public var containerID: String { - get {return _storage._containerID} + get {_storage._containerID} set {_uniqueStorage()._containerID = newValue} } public var process: Com_Apple_Containerization_Sandbox_V3_ProcessStats { - get {return _storage._process ?? Com_Apple_Containerization_Sandbox_V3_ProcessStats()} + get {_storage._process ?? Com_Apple_Containerization_Sandbox_V3_ProcessStats()} set {_uniqueStorage()._process = newValue} } /// Returns true if `process` has been explicitly set. - public var hasProcess: Bool {return _storage._process != nil} + public var hasProcess: Bool {_storage._process != nil} /// Clears the value of `process`. Subsequent reads from it will return its default value. public mutating func clearProcess() {_uniqueStorage()._process = nil} public var memory: Com_Apple_Containerization_Sandbox_V3_MemoryStats { - get {return _storage._memory ?? Com_Apple_Containerization_Sandbox_V3_MemoryStats()} + get {_storage._memory ?? Com_Apple_Containerization_Sandbox_V3_MemoryStats()} set {_uniqueStorage()._memory = newValue} } /// Returns true if `memory` has been explicitly set. - public var hasMemory: Bool {return _storage._memory != nil} + public var hasMemory: Bool {_storage._memory != nil} /// Clears the value of `memory`. Subsequent reads from it will return its default value. public mutating func clearMemory() {_uniqueStorage()._memory = nil} public var cpu: Com_Apple_Containerization_Sandbox_V3_CPUStats { - get {return _storage._cpu ?? Com_Apple_Containerization_Sandbox_V3_CPUStats()} + get {_storage._cpu ?? Com_Apple_Containerization_Sandbox_V3_CPUStats()} set {_uniqueStorage()._cpu = newValue} } /// Returns true if `cpu` has been explicitly set. - public var hasCpu: Bool {return _storage._cpu != nil} + public var hasCpu: Bool {_storage._cpu != nil} /// Clears the value of `cpu`. Subsequent reads from it will return its default value. public mutating func clearCpu() {_uniqueStorage()._cpu = nil} public var blockIo: Com_Apple_Containerization_Sandbox_V3_BlockIOStats { - get {return _storage._blockIo ?? Com_Apple_Containerization_Sandbox_V3_BlockIOStats()} + get {_storage._blockIo ?? Com_Apple_Containerization_Sandbox_V3_BlockIOStats()} set {_uniqueStorage()._blockIo = newValue} } /// Returns true if `blockIo` has been explicitly set. - public var hasBlockIo: Bool {return _storage._blockIo != nil} + public var hasBlockIo: Bool {_storage._blockIo != nil} /// Clears the value of `blockIo`. Subsequent reads from it will return its default value. public mutating func clearBlockIo() {_uniqueStorage()._blockIo = nil} public var networks: [Com_Apple_Containerization_Sandbox_V3_NetworkStats] { - get {return _storage._networks} + get {_storage._networks} set {_uniqueStorage()._networks = newValue} } public var memoryEvents: Com_Apple_Containerization_Sandbox_V3_MemoryEventStats { - get {return _storage._memoryEvents ?? Com_Apple_Containerization_Sandbox_V3_MemoryEventStats()} + get {_storage._memoryEvents ?? Com_Apple_Containerization_Sandbox_V3_MemoryEventStats()} set {_uniqueStorage()._memoryEvents = newValue} } /// Returns true if `memoryEvents` has been explicitly set. - public var hasMemoryEvents: Bool {return _storage._memoryEvents != nil} + public var hasMemoryEvents: Bool {_storage._memoryEvents != nil} /// Clears the value of `memoryEvents`. Subsequent reads from it will return its default value. public mutating func clearMemoryEvents() {_uniqueStorage()._memoryEvents = nil} @@ -1448,24 +1448,12 @@ public struct Com_Apple_Containerization_Sandbox_V3_MemoryEventStats: Sendable { fileprivate let _protobuf_package = "com.apple.containerization.sandbox.v3" extension Com_Apple_Containerization_Sandbox_V3_StatCategory: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "STAT_CATEGORY_UNSPECIFIED"), - 1: .same(proto: "STAT_CATEGORY_PROCESS"), - 2: .same(proto: "STAT_CATEGORY_MEMORY"), - 3: .same(proto: "STAT_CATEGORY_CPU"), - 4: .same(proto: "STAT_CATEGORY_BLOCK_IO"), - 5: .same(proto: "STAT_CATEGORY_NETWORK"), - 6: .same(proto: "STAT_CATEGORY_MEMORY_EVENTS"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0STAT_CATEGORY_UNSPECIFIED\0\u{1}STAT_CATEGORY_PROCESS\0\u{1}STAT_CATEGORY_MEMORY\0\u{1}STAT_CATEGORY_CPU\0\u{1}STAT_CATEGORY_BLOCK_IO\0\u{1}STAT_CATEGORY_NETWORK\0\u{1}STAT_CATEGORY_MEMORY_EVENTS\0") } extension Com_Apple_Containerization_Sandbox_V3_Stdio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Stdio" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "stdinPort"), - 2: .same(proto: "stdoutPort"), - 3: .same(proto: "stderrPort"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}stdinPort\0\u{1}stdoutPort\0\u{1}stderrPort\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1509,15 +1497,7 @@ extension Com_Apple_Containerization_Sandbox_V3_Stdio: SwiftProtobuf.Message, Sw extension Com_Apple_Containerization_Sandbox_V3_SetupEmulatorRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SetupEmulatorRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "binary_path"), - 2: .same(proto: "name"), - 3: .same(proto: "type"), - 4: .same(proto: "offset"), - 5: .same(proto: "magic"), - 6: .same(proto: "mask"), - 7: .same(proto: "flags"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}binary_path\0\u{1}name\0\u{1}type\0\u{1}offset\0\u{1}magic\0\u{1}mask\0\u{1}flags\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1596,10 +1576,7 @@ extension Com_Apple_Containerization_Sandbox_V3_SetupEmulatorResponse: SwiftProt extension Com_Apple_Containerization_Sandbox_V3_SetTimeRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SetTimeRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "sec"), - 2: .same(proto: "usec"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}sec\0\u{1}usec\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1653,9 +1630,7 @@ extension Com_Apple_Containerization_Sandbox_V3_SetTimeResponse: SwiftProtobuf.M extension Com_Apple_Containerization_Sandbox_V3_SysctlRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SysctlRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "settings"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}settings\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1704,13 +1679,7 @@ extension Com_Apple_Containerization_Sandbox_V3_SysctlResponse: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ProxyVsockRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .standard(proto: "vsock_port"), - 3: .same(proto: "guestPath"), - 4: .same(proto: "guestSocketPermissions"), - 5: .same(proto: "action"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}vsock_port\0\u{1}guestPath\0\u{1}guestSocketPermissions\0\u{1}action\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1763,10 +1732,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest: SwiftProtobuf } extension Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest.Action: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "INTO"), - 1: .same(proto: "OUT_OF"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0INTO\0\u{1}OUT_OF\0") } extension Com_Apple_Containerization_Sandbox_V3_ProxyVsockResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { @@ -1790,9 +1756,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ProxyVsockResponse: SwiftProtobu extension Com_Apple_Containerization_Sandbox_V3_StopVsockProxyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".StopVsockProxyRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1841,12 +1805,7 @@ extension Com_Apple_Containerization_Sandbox_V3_StopVsockProxyResponse: SwiftPro extension Com_Apple_Containerization_Sandbox_V3_MountRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MountRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "source"), - 3: .same(proto: "destination"), - 4: .same(proto: "options"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}type\0\u{1}source\0\u{1}destination\0\u{1}options\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1910,10 +1869,7 @@ extension Com_Apple_Containerization_Sandbox_V3_MountResponse: SwiftProtobuf.Mes extension Com_Apple_Containerization_Sandbox_V3_UmountRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".UmountRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "path"), - 2: .same(proto: "flags"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}flags\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -1967,10 +1923,7 @@ extension Com_Apple_Containerization_Sandbox_V3_UmountResponse: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_SetenvRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SetenvRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "key"), - 2: .same(proto: "value"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}key\0\u{1}value\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2028,9 +1981,7 @@ extension Com_Apple_Containerization_Sandbox_V3_SetenvResponse: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_GetenvRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".GetenvRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "key"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}key\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2060,9 +2011,7 @@ extension Com_Apple_Containerization_Sandbox_V3_GetenvRequest: SwiftProtobuf.Mes extension Com_Apple_Containerization_Sandbox_V3_GetenvResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".GetenvResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "value"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}value\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2096,16 +2045,7 @@ extension Com_Apple_Containerization_Sandbox_V3_GetenvResponse: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CreateProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - 3: .same(proto: "stdin"), - 4: .same(proto: "stdout"), - 5: .same(proto: "stderr"), - 6: .same(proto: "ociRuntimePath"), - 7: .same(proto: "configuration"), - 8: .same(proto: "options"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0\u{1}stdin\0\u{1}stdout\0\u{1}stderr\0\u{1}ociRuntimePath\0\u{1}configuration\0\u{1}options\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2193,10 +2133,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessResponse: SwiftProt extension Com_Apple_Containerization_Sandbox_V3_WaitProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WaitProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2235,10 +2172,7 @@ extension Com_Apple_Containerization_Sandbox_V3_WaitProcessRequest: SwiftProtobu extension Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WaitProcessResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "exitCode"), - 2: .standard(proto: "exited_at"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}exitCode\0\u{3}exited_at\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2277,12 +2211,7 @@ extension Com_Apple_Containerization_Sandbox_V3_WaitProcessResponse: SwiftProtob extension Com_Apple_Containerization_Sandbox_V3_ResizeProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ResizeProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - 3: .same(proto: "rows"), - 4: .same(proto: "columns"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0\u{1}rows\0\u{1}columns\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2350,10 +2279,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ResizeProcessResponse: SwiftProt extension Com_Apple_Containerization_Sandbox_V3_DeleteProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".DeleteProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2411,10 +2337,7 @@ extension Com_Apple_Containerization_Sandbox_V3_DeleteProcessResponse: SwiftProt extension Com_Apple_Containerization_Sandbox_V3_StartProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".StartProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2453,9 +2376,7 @@ extension Com_Apple_Containerization_Sandbox_V3_StartProcessRequest: SwiftProtob extension Com_Apple_Containerization_Sandbox_V3_StartProcessResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".StartProcessResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "pid"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}pid\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2485,11 +2406,7 @@ extension Com_Apple_Containerization_Sandbox_V3_StartProcessResponse: SwiftProto extension Com_Apple_Containerization_Sandbox_V3_KillProcessRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".KillProcessRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - 3: .same(proto: "signal"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0\u{1}signal\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2533,9 +2450,7 @@ extension Com_Apple_Containerization_Sandbox_V3_KillProcessRequest: SwiftProtobu extension Com_Apple_Containerization_Sandbox_V3_KillProcessResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".KillProcessResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "result"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2565,10 +2480,7 @@ extension Com_Apple_Containerization_Sandbox_V3_KillProcessResponse: SwiftProtob extension Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CloseProcessStdinRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "containerID"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}containerID\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2626,11 +2538,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse: Swift extension Com_Apple_Containerization_Sandbox_V3_MkdirRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MkdirRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "path"), - 2: .same(proto: "all"), - 3: .same(proto: "perms"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}all\0\u{1}perms\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2689,12 +2597,7 @@ extension Com_Apple_Containerization_Sandbox_V3_MkdirResponse: SwiftProtobuf.Mes extension Com_Apple_Containerization_Sandbox_V3_WriteFileRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".WriteFileRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "path"), - 2: .same(proto: "data"), - 3: .same(proto: "mode"), - 4: .same(proto: "flags"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}data\0\u{1}mode\0\u{1}flags\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2743,11 +2646,7 @@ extension Com_Apple_Containerization_Sandbox_V3_WriteFileRequest: SwiftProtobuf. extension Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.WriteFileFlags: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Com_Apple_Containerization_Sandbox_V3_WriteFileRequest.protoMessageName + ".WriteFileFlags" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "create_parent_dirs"), - 2: .same(proto: "append"), - 3: .standard(proto: "create_if_missing"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}create_parent_dirs\0\u{1}append\0\u{3}create_if_missing\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2806,10 +2705,7 @@ extension Com_Apple_Containerization_Sandbox_V3_WriteFileResponse: SwiftProtobuf extension Com_Apple_Containerization_Sandbox_V3_CopyInChunk: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CopyInChunk" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "init"), - 2: .same(proto: "data"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}init\0\u{1}data\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2871,11 +2767,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyInChunk: SwiftProtobuf.Messa extension Com_Apple_Containerization_Sandbox_V3_CopyInInit: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CopyInInit" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "path"), - 2: .same(proto: "mode"), - 3: .standard(proto: "create_parents"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}mode\0\u{3}create_parents\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2934,9 +2826,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyInResponse: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CopyOutRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "path"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2966,10 +2856,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_CopyOutChunk: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CopyOutChunk" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "init"), - 2: .same(proto: "data"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}init\0\u{1}data\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3031,9 +2918,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutChunk: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_CopyOutInit: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CopyOutInit" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "total_size"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}total_size\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3063,11 +2948,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutInit: SwiftProtobuf.Messa extension Com_Apple_Containerization_Sandbox_V3_IpLinkSetRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".IpLinkSetRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - 2: .same(proto: "up"), - 3: .same(proto: "mtu"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}up\0\u{1}mtu\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3130,10 +3011,7 @@ extension Com_Apple_Containerization_Sandbox_V3_IpLinkSetResponse: SwiftProtobuf extension Com_Apple_Containerization_Sandbox_V3_IpAddrAddRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".IpAddrAddRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - 2: .same(proto: "ipv4Address"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}ipv4Address\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3187,11 +3065,7 @@ extension Com_Apple_Containerization_Sandbox_V3_IpAddrAddResponse: SwiftProtobuf extension Com_Apple_Containerization_Sandbox_V3_IpRouteAddLinkRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".IpRouteAddLinkRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - 2: .same(proto: "dstIpv4Addr"), - 3: .same(proto: "srcIpv4Addr"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}dstIpv4Addr\0\u{1}srcIpv4Addr\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3250,10 +3124,7 @@ extension Com_Apple_Containerization_Sandbox_V3_IpRouteAddLinkResponse: SwiftPro extension Com_Apple_Containerization_Sandbox_V3_IpRouteAddDefaultRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".IpRouteAddDefaultRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - 2: .same(proto: "ipv4Gateway"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}ipv4Gateway\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3307,13 +3178,7 @@ extension Com_Apple_Containerization_Sandbox_V3_IpRouteAddDefaultResponse: Swift extension Com_Apple_Containerization_Sandbox_V3_ConfigureDnsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ConfigureDnsRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "location"), - 2: .same(proto: "nameservers"), - 3: .same(proto: "domain"), - 4: .same(proto: "searchDomains"), - 5: .same(proto: "options"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}location\0\u{1}nameservers\0\u{1}domain\0\u{1}searchDomains\0\u{1}options\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3386,11 +3251,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ConfigureDnsResponse: SwiftProto extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ConfigureHostsRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "location"), - 2: .same(proto: "entries"), - 3: .same(proto: "comment"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}location\0\u{1}entries\0\u{1}comment\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3434,11 +3295,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest: SwiftProt extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.HostsEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest.protoMessageName + ".HostsEntry" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "ipAddress"), - 2: .same(proto: "hostnames"), - 3: .same(proto: "comment"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}ipAddress\0\u{1}hostnames\0\u{1}comment\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3539,10 +3396,7 @@ extension Com_Apple_Containerization_Sandbox_V3_SyncResponse: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_KillRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".KillRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "pid"), - 3: .same(proto: "signal"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}pid\0\u{2}\u{2}signal\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3577,9 +3431,7 @@ extension Com_Apple_Containerization_Sandbox_V3_KillRequest: SwiftProtobuf.Messa extension Com_Apple_Containerization_Sandbox_V3_KillResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".KillResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "result"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3609,10 +3461,7 @@ extension Com_Apple_Containerization_Sandbox_V3_KillResponse: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ContainerStatisticsRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "container_ids"), - 2: .same(proto: "categories"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}container_ids\0\u{1}categories\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3647,9 +3496,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest: Swif extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ContainerStatisticsResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "containers"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}containers\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3679,15 +3526,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse: Swi extension Com_Apple_Containerization_Sandbox_V3_ContainerStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ContainerStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "container_id"), - 2: .same(proto: "process"), - 3: .same(proto: "memory"), - 4: .same(proto: "cpu"), - 5: .standard(proto: "block_io"), - 6: .same(proto: "networks"), - 7: .standard(proto: "memory_events"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}container_id\0\u{1}process\0\u{1}memory\0\u{1}cpu\0\u{3}block_io\0\u{1}networks\0\u{3}memory_events\0") fileprivate class _StorageClass { var _containerID: String = String() @@ -3799,10 +3638,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ContainerStats: SwiftProtobuf.Me extension Com_Apple_Containerization_Sandbox_V3_ProcessStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ProcessStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "current"), - 2: .same(proto: "limit"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}current\0\u{1}limit\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3837,17 +3673,7 @@ extension Com_Apple_Containerization_Sandbox_V3_ProcessStats: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_MemoryStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MemoryStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "usage_bytes"), - 2: .standard(proto: "limit_bytes"), - 3: .standard(proto: "swap_usage_bytes"), - 4: .standard(proto: "swap_limit_bytes"), - 5: .standard(proto: "cache_bytes"), - 6: .standard(proto: "kernel_stack_bytes"), - 7: .standard(proto: "slab_bytes"), - 8: .standard(proto: "page_faults"), - 9: .standard(proto: "major_page_faults"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}usage_bytes\0\u{3}limit_bytes\0\u{3}swap_usage_bytes\0\u{3}swap_limit_bytes\0\u{3}cache_bytes\0\u{3}kernel_stack_bytes\0\u{3}slab_bytes\0\u{3}page_faults\0\u{3}major_page_faults\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3917,14 +3743,7 @@ extension Com_Apple_Containerization_Sandbox_V3_MemoryStats: SwiftProtobuf.Messa extension Com_Apple_Containerization_Sandbox_V3_CPUStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".CPUStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "usage_usec"), - 2: .standard(proto: "user_usec"), - 3: .standard(proto: "system_usec"), - 4: .standard(proto: "throttling_periods"), - 5: .standard(proto: "throttled_periods"), - 6: .standard(proto: "throttled_time_usec"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}usage_usec\0\u{3}user_usec\0\u{3}system_usec\0\u{3}throttling_periods\0\u{3}throttled_periods\0\u{3}throttled_time_usec\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -3979,9 +3798,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CPUStats: SwiftProtobuf.Message, extension Com_Apple_Containerization_Sandbox_V3_BlockIOStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BlockIOStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "devices"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}devices\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4011,14 +3828,7 @@ extension Com_Apple_Containerization_Sandbox_V3_BlockIOStats: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_BlockIOEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BlockIOEntry" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "major"), - 2: .same(proto: "minor"), - 3: .standard(proto: "read_bytes"), - 4: .standard(proto: "write_bytes"), - 5: .standard(proto: "read_operations"), - 6: .standard(proto: "write_operations"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}major\0\u{1}minor\0\u{3}read_bytes\0\u{3}write_bytes\0\u{3}read_operations\0\u{3}write_operations\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4073,15 +3883,7 @@ extension Com_Apple_Containerization_Sandbox_V3_BlockIOEntry: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".NetworkStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - 2: .same(proto: "receivedPackets"), - 3: .same(proto: "transmittedPackets"), - 4: .same(proto: "receivedBytes"), - 5: .same(proto: "transmittedBytes"), - 6: .same(proto: "receivedErrors"), - 7: .same(proto: "transmittedErrors"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}receivedPackets\0\u{1}transmittedPackets\0\u{1}receivedBytes\0\u{1}transmittedBytes\0\u{1}receivedErrors\0\u{1}transmittedErrors\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4141,14 +3943,7 @@ extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftProtobuf.Mess extension Com_Apple_Containerization_Sandbox_V3_MemoryEventStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MemoryEventStats" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "low"), - 2: .same(proto: "high"), - 3: .same(proto: "max"), - 4: .same(proto: "oom"), - 5: .standard(proto: "oom_kill"), - 6: .standard(proto: "oom_group_kill"), - ] + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}low\0\u{1}high\0\u{1}max\0\u{1}oom\0\u{3}oom_kill\0\u{3}oom_group_kill\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4199,4 +3994,4 @@ extension Com_Apple_Containerization_Sandbox_V3_MemoryEventStats: SwiftProtobuf. if lhs.unknownFields != rhs.unknownFields {return false} return true } -} +} \ No newline at end of file diff --git a/Sources/ContainerizationExtras/IPAddress+OS.swift b/Sources/ContainerizationExtras/IPAddress+OS.swift new file mode 100644 index 00000000..1a58d5ea --- /dev/null +++ b/Sources/ContainerizationExtras/IPAddress+OS.swift @@ -0,0 +1,177 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +#if canImport(Musl) +import Musl +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#else +#error("Platform not supported.") +#endif + +#if canImport(Darwin) +import Darwin +private let AF_LINK_TYPE = AF_LINK +#elseif canImport(Glibc) || canImport(Musl) +private let AF_LINK_TYPE = AF_PACKET +#endif + +extension sockaddr_in6 { + public init(address: IPv6Address) throws { + self.init() + self.sin6_family = sa_family_t(AF_INET6) + self.sin6_port = 0 + self.sin6_flowinfo = 0 + self.sin6_scope_id = try address.scopeId() + withUnsafeMutableBytes(of: &self.sin6_addr) { ptr in + ptr.copyBytes(from: address.bytes) + } + } + + public func toIPv6Address() throws -> IPv6Address { + let bytes: [UInt8] = withUnsafeBytes(of: sin6_addr) { ptr in + [UInt8](ptr) // Using bracket notation + } + return try IPv6Address(bytes) + } +} + +extension IPv6Address { + public func scopeId() throws -> UInt32 { + guard let zone else { + return 0 + } + if let scopeId = UInt32(zone) { + return scopeId + } + let scopeId = if_nametoindex(zone) + guard scopeId > 0 else { + throw AddressError.invalidZoneIdentifier + } + return scopeId + } +} + +extension MACAddress { + /// Get the MAC address for a network interface + /// - Parameter zone: Interface name (e.g., "en0") or interface index (e.g., "1") + /// - Returns: MAC address for the interface, or nil if not found + public static func fromZone(_ zone: String) -> MACAddress? { + // Convert zone to interface name if it's a numeric index + let ifname: String + if let index = UInt32(zone) { + var buffer = [CChar](repeating: 0, count: Int(IF_NAMESIZE)) + guard if_indextoname(index, &buffer) != nil else { + return nil + } + // Convert CChar to UInt8 and truncate at null terminator + let bytes = buffer.prefix(while: { $0 != 0 }).map { UInt8(bitPattern: $0) } + guard let name = String(validating: bytes, as: UTF8.self) else { + return nil + } + ifname = name + } else { + ifname = zone + } + + #if canImport(Darwin) + // Darwin: Use getifaddrs() to find AF_LINK address + var ifaddrs: UnsafeMutablePointer? + guard getifaddrs(&ifaddrs) == 0 else { + return nil + } + defer { freeifaddrs(ifaddrs) } + + var currentIfaddr = ifaddrs + while let ifaddr = currentIfaddr { + defer { currentIfaddr = ifaddr.pointee.ifa_next } + + guard let name = ifaddr.pointee.ifa_name, + String(cString: name) == ifname, + let addr = ifaddr.pointee.ifa_addr + else { + continue + } + + guard addr.pointee.sa_family == AF_LINK else { + continue + } + + let sdl = addr.withMemoryRebound(to: sockaddr_dl.self, capacity: 1) { $0.pointee } + let macOffset = Int(sdl.sdl_nlen) + let macLen = Int(sdl.sdl_alen) + + guard macLen == 6 else { + continue + } + + var macBytes = [UInt8](repeating: 0, count: 6) + withUnsafeBytes(of: sdl.sdl_data) { ptr in + let start = ptr.baseAddress!.advanced(by: macOffset) + macBytes.withUnsafeMutableBytes { dst in + dst.copyBytes(from: UnsafeRawBufferPointer(start: start, count: 6)) + } + } + + return try? MACAddress(macBytes) + } + + return nil + #elseif canImport(Glibc) || canImport(Musl) + // Linux: Use ioctl with SIOCGIFHWADDR to get hardware address + #if canImport(Glibc) + let osSockDgram = Int32(SOCK_DGRAM.rawValue) + #else + let osSockDgram = Int32(SOCK_DGRAM) + #endif + let fd = socket(AF_INET, osSockDgram, 0) + guard fd >= 0 else { + return nil + } + defer { close(fd) } + + var ifr = ifreq() + + // Copy interface name into ifr_ifrn.ifrn_name + guard ifname.utf8.count < MemoryLayout.size(ofValue: ifr.ifr_ifrn.ifrn_name) else { + return nil + } + + withUnsafeMutableBytes(of: &ifr.ifr_ifrn.ifrn_name) { ptr in + _ = ifname.utf8.withContiguousStorageIfAvailable { utf8 in + ptr.copyBytes(from: UnsafeRawBufferPointer(start: utf8.baseAddress, count: utf8.count)) + } + } + + // Get hardware address + guard ioctl(fd, UInt(SIOCGIFHWADDR), &ifr) >= 0 else { + return nil + } + + // Extract MAC address from ifr_hwaddr.sa_data + var macBytes = [UInt8](repeating: 0, count: 6) + withUnsafeBytes(of: ifr.ifr_ifru.ifru_hwaddr.sa_data) { ptr in + macBytes.withUnsafeMutableBytes { dst in + dst.copyBytes(from: UnsafeRawBufferPointer(start: ptr.baseAddress, count: 6)) + } + } + + return try? MACAddress(macBytes) + #endif + } +} diff --git a/Sources/ContainerizationICMP/Echo.swift b/Sources/ContainerizationICMP/Echo.swift new file mode 100644 index 00000000..33d72325 --- /dev/null +++ b/Sources/ContainerizationICMP/Echo.swift @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras +import Foundation + +public struct Echo: Bindable { + public static let size = 4 + + public var identifier: UInt16 + public var sequenceNumber: UInt16 + + public init( + identifier: UInt16, + sequenceNumber: UInt16 + ) throws { + self.identifier = identifier + self.sequenceNumber = sequenceNumber + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(as: UInt16.self, value: identifier.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "Echo", field: "identifier") + } + guard let offset = buffer.copyIn(as: UInt16.self, value: sequenceNumber.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "Echo", field: "sequenceNumber") + } + + assert(offset - startOffset == Self.size, "BUG: echo appendBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "Echo", field: "identifier") + } + identifier = UInt16(bigEndian: value) + + guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "Echo", field: "sequenceNumber") + } + sequenceNumber = UInt16(bigEndian: value) + + assert(offset - startOffset == Self.size, "BUG: echo bindBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } +} + +package protocol EchoPayload {} + +extension EchoPayload { + public static var size: Int { + 56 + } +} + +public struct EchoPayloadRTT: EchoPayload { + public var date: Date + + public init(date: Date? = nil) { + self.date = date ?? Date() + } + + public func rtt(atDate: Date? = nil) -> TimeInterval { + (atDate ?? Date()).timeIntervalSince(date) + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + let timeInterval = date.timeIntervalSinceReferenceDate + guard let _ = buffer.copyIn(as: UInt64.self, value: timeInterval.bitPattern, offset: offset) else { + throw BindError.sendMarshalFailure(type: "EchoPayloadRTT", field: "date") + } + + let finalOffset = offset + Self.size + assert(finalOffset - startOffset == Self.size, "BUG: echo payload RTT appendBuffer length mismatch - expected \(Self.size), got \(finalOffset - startOffset)") + return finalOffset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (_, value) = buffer.copyOut(as: UInt64.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "EchoPayloadRTT", field: "date") + } + let timeInterval = Double(bitPattern: value) + date = Date(timeIntervalSinceReferenceDate: timeInterval) + + let finalOffset = offset + Self.size + assert(finalOffset - startOffset == Self.size, "BUG: echo payload RTT bindBuffer length mismatch - expected \(Self.size), got \(finalOffset - startOffset)") + return finalOffset + } +} diff --git a/Sources/ContainerizationICMP/ICMPError.swift b/Sources/ContainerizationICMP/ICMPError.swift new file mode 100644 index 00000000..3f5585bb --- /dev/null +++ b/Sources/ContainerizationICMP/ICMPError.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +/// Errors that may occur during ICMP operations +public enum ICMPError: Swift.Error, CustomStringConvertible { + case invalidPacket(String) + case invalidResponse(String) + case timeout + case unexpectedMessageType(expected: UInt8, got: UInt8) + case bufferTooSmall(needed: Int, available: Int) + case cancelled + + public var description: String { + switch self { + case .invalidPacket(let message): + return "packet validation error: \(message)" + case .invalidResponse(let message): + return "invalid response: \(message)" + case .timeout: + return "request timed out" + case .unexpectedMessageType(let expected, let got): + return "unexpected message type: expected \(expected), got \(got)" + case .bufferTooSmall(let needed, let available): + return "buffer too small: needed \(needed), available \(available)" + case .cancelled: + return "operation was cancelled" + } + } +} diff --git a/Sources/ContainerizationICMP/ICMPMessage.swift b/Sources/ContainerizationICMP/ICMPMessage.swift new file mode 100644 index 00000000..b67cc6a2 --- /dev/null +++ b/Sources/ContainerizationICMP/ICMPMessage.swift @@ -0,0 +1,184 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras + +public enum ICMPv4MessageType: UInt8, Sendable { + case echoReply = 0 + case destinationUnreachable = 3 + case sourceQuench = 4 // Deprecated (RFC 6633) + case redirect = 5 + case echoRequest = 8 + case routerAdvertisement = 9 + case routerSolicitation = 10 + case timeExceeded = 11 + case parameterProblem = 12 + case timestampRequest = 13 + case timestampReply = 14 + case informationRequest = 15 // Obsolete + case informationReply = 16 // Obsolete + case addressMaskRequest = 17 // Deprecated + case addressMaskReply = 18 // Deprecated + case traceroute = 30 // Deprecated (RFC 1393) +} + +public struct ICMPv4Header: Bindable { + public static let size: Int = 4 + + public var type: ICMPv4MessageType + + public var code: UInt8 + + public init(type: ICMPv4MessageType = .echoReply, code: UInt8 = 0) { + self.type = type + self.code = code + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(as: UInt8.self, value: type.rawValue, offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv4Header", field: "type") + } + guard let offset = buffer.copyIn(as: UInt8.self, value: code, offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv4Header", field: "code") + } + guard let offset = buffer.copyIn(as: UInt16.self, value: UInt16(0), offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv4Header", field: "checksum") + } + + assert(offset - startOffset == Self.size, "BUG: echo appendBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "ICMPv4Header", field: "type") + } + guard let type = ICMPv4MessageType(rawValue: value) else { + throw BindError.recvMarshalFailure(type: "ICMPv4Header", field: "type") + } + self.type = type + + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "ICMPv4Header", field: "code") + } + self.code = value + + let offsetSkippingChecksum = offset + MemoryLayout.size + + assert(offsetSkippingChecksum - startOffset == Self.size, "BUG: echo bindBuffer length mismatch - expected \(Self.size), got \(offsetSkippingChecksum - startOffset)") + return offsetSkippingChecksum + } + + /// Calculate ICMPv4 checksum (16-bit one's complement of one's complement sum) + public static func checksum(buffer: [UInt8], offset: Int, length: Int) -> UInt16 { + var sum: UInt32 = 0 + var i = offset + let end = offset + length + + // Sum up 16-bit words + while i < end - 1 { + let word = UInt32(buffer[i]) << 8 | UInt32(buffer[i + 1]) + sum += word + i += 2 + } + + // Add remaining byte if odd length + if i < end { + sum += UInt32(buffer[i]) << 8 + } + + // Fold 32-bit sum to 16 bits + while (sum >> 16) != 0 { + sum = (sum & 0xFFFF) + (sum >> 16) + } + + // One's complement + return ~UInt16(sum & 0xFFFF) + } +} + +public enum ICMPv6MessageType: UInt8, Sendable { + case echoRequest = 128 + case echoReply = 129 + case routerSolicitation = 133 + case routerAdvertisement = 134 + case neighborSolicitation = 135 + case neighborAdvertisement = 136 +} + +public struct ICMPv6Header: Bindable { + public static let size: Int = 4 + + public var type: ICMPv6MessageType + + public var code: UInt8 + + public init(type: ICMPv6MessageType = .echoReply, code: UInt8 = 0) { + self.type = type + self.code = code + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(as: UInt8.self, value: type.rawValue, offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv6Header", field: "type") + } + guard let offset = buffer.copyIn(as: UInt8.self, value: code, offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv6Header", field: "code") + } + guard let offset = buffer.copyIn(as: UInt16.self, value: UInt16(0), offset: offset) else { + throw BindError.sendMarshalFailure(type: "ICMPv6Header", field: "checksum") + } + + assert(offset - startOffset == Self.size, "BUG: echo appendBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "ICMPv6Header", field: "type") + } + guard let type = ICMPv6MessageType(rawValue: value) else { + throw BindError.recvMarshalFailure(type: "ICMPv6Header", field: "type") + } + self.type = type + + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "ICMPv6Header", field: "code") + } + self.code = value + + let offsetSkippingChecksum = offset + MemoryLayout.size + + assert(offsetSkippingChecksum - startOffset == Self.size, "BUG: echo bindBuffer length mismatch - expected \(Self.size), got \(offsetSkippingChecksum - startOffset)") + return offsetSkippingChecksum + } +} + +extension ICMPv4Header { + public func matches(type: ICMPv4MessageType, code: UInt8 = 0) -> Bool { + self.type == type && self.code == code + } +} + +extension ICMPv6Header { + public func matches(type: ICMPv6MessageType, code: UInt8 = 0) -> Bool { + self.type == type && self.code == code + } +} diff --git a/Sources/ContainerizationICMP/ICMPSession.swift b/Sources/ContainerizationICMP/ICMPSession.swift new file mode 100644 index 00000000..cb847374 --- /dev/null +++ b/Sources/ContainerizationICMP/ICMPSession.swift @@ -0,0 +1,205 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras +import Foundation + +/// Session for sending and receiving ICMPv4 messages. +public final class ICMPv4Session: Sendable { + private static let receiveBufferSize = 65536 + + private let socket: ICMPv4Socket + + public init() throws { + self.socket = try ICMPv4Socket() + } + + // MARK: - ICMPv4 Echo (Ping) + + /// Send an ICMPv4 echo request and wait for a reply + /// - Parameters: + /// - host: The hostname or IP address to ping + /// - identifier: The identifier for the echo request (typically process ID) + /// - sequenceNumber: The sequence number for this ping + /// - payload: Optional payload data (default is 56-byte RTT timestamp) + /// - timeout: Maximum time to wait for a reply (default: 5 seconds) + /// - Returns: Tuple of (reply, round-trip time in seconds, source address) + public func echoRequest( + ipAddress: IPv4Address, + identifier: UInt16, + sequenceNumber: UInt16 + ) throws { + let totalSize = ICMPv4Header.size + Echo.size + EchoPayloadRTT.size + var buffer = [UInt8](repeating: 0, count: totalSize) + var offset = 0 + + let header = ICMPv4Header(type: .echoRequest, code: 0) + offset = try header.appendBuffer(&buffer, offset: offset) + + let echo = try Echo(identifier: identifier, sequenceNumber: sequenceNumber) + offset = try echo.appendBuffer(&buffer, offset: offset) + + let payload = EchoPayloadRTT() + offset = try payload.appendBuffer(&buffer, offset: offset) + + assert(offset == totalSize) + + let checksum = ICMPv4Header.checksum(buffer: buffer, offset: 0, length: totalSize) + buffer[2] = UInt8((checksum >> 8) & 0xFF) + buffer[3] = UInt8(checksum & 0xFF) + _ = try socket.send(buffer: buffer, to: ipAddress) + } + + public func recvHeader() throws -> (sourceAddr: IPv4Address, header: ICMPv4Header, bytes: [UInt8], length: Int, offset: Int) { + var buffer = [UInt8](repeating: 0, count: Self.receiveBufferSize) + let (bytesReceived, ipAddr) = try socket.receive(buffer: &buffer) + + // Skip IPv4 header + guard bytesReceived > 0 else { + throw ICMPError.bufferTooSmall(needed: 1, available: bytesReceived) + } + + let ipHeaderLength = Int((buffer[0] & 0x0F)) * 4 + var offset = ipHeaderLength + + guard bytesReceived >= offset + ICMPv4Header.size else { + throw ICMPError.bufferTooSmall(needed: offset + ICMPv4Header.size, available: bytesReceived) + } + + var header = ICMPv4Header() + offset = try header.bindBuffer(&buffer, offset: offset) + + return (sourceAddr: ipAddr, header: header, bytes: buffer, length: bytesReceived, offset: offset) + } +} + +/// Session for sending and receiving ICMPv6 messages. +public final class ICMPv6Session: Sendable { + private static let receiveBufferSize = 65536 + + private static let unspecifiedAddress = IPv6Address(0) + private static func allRoutersMulticastAddress(zone: String?) -> IPv6Address { + IPv6Address(0xFF02_0000_0000_0000_0000_0000_0000_0002, zone: zone) + } + + private let socket: ICMPv6Socket + + public init() throws { + self.socket = try ICMPv6Socket() + } + + /// Send an ICMPv6 echo request and wait for a reply + /// - Parameters: + /// - host: The hostname or IP address to ping + /// - identifier: The identifier for the echo request (typically process ID) + /// - sequenceNumber: The sequence number for this ping + /// - payload: Optional payload data (default is 56-byte RTT timestamp) + /// - timeout: Maximum time to wait for a reply (default: 5 seconds) + /// - Returns: Tuple of (reply, round-trip time in seconds, source address) + public func echoRequest( + ipAddress: IPv6Address, + identifier: UInt16, + sequenceNumber: UInt16, + ) throws { + let totalSize = ICMPv6Header.size + Echo.size + EchoPayloadRTT.size + var buffer = [UInt8](repeating: 0, count: totalSize) + var offset = 0 + + let header = ICMPv6Header(type: .echoRequest, code: 0) + offset = try header.appendBuffer(&buffer, offset: offset) + + let echo = try Echo(identifier: identifier, sequenceNumber: sequenceNumber) + offset = try echo.appendBuffer(&buffer, offset: offset) + + let payload = EchoPayloadRTT() + offset = try payload.appendBuffer(&buffer, offset: offset) + + assert(offset == totalSize) + + _ = try socket.send(buffer: buffer, to: ipAddress) + } + + public func routerSolicitation(linkLayerAddress: MACAddress?, interface: String?) throws { + let totalSize: Int + if linkLayerAddress == nil { + totalSize = ICMPv6Header.size + MemoryLayout.size + } else { + totalSize = ICMPv6Header.size + MemoryLayout.size + NDOptionHeader.size + SourceLinkLayerAddress.size + } + + var buffer = [UInt8](repeating: 0, count: totalSize) + var offset = 0 + + let header = ICMPv6Header(type: .routerSolicitation, code: 0) + offset = try header.appendBuffer(&buffer, offset: offset) + + guard var offset = buffer.copyIn(as: UInt32.self, value: 0, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterSolicitation", field: "reserved") + } + + if let address = linkLayerAddress { + let option = NDOption.sourceLinkLayerAddress(address) + offset = try option.appendBuffer(&buffer, offset: offset) + } + + assert(offset == totalSize) + + _ = try socket.send(buffer: buffer, to: Self.allRoutersMulticastAddress(zone: interface)) + } + + public func recvHeader() throws -> (sourceAddr: IPv6Address, header: ICMPv6Header, bytes: [UInt8], length: Int, offset: Int) { + var buffer = [UInt8](repeating: 0, count: Self.receiveBufferSize) + let (bytesReceived, ipAddr) = try socket.receive(buffer: &buffer) + + guard bytesReceived >= ICMPv6Header.size else { + throw ICMPError.bufferTooSmall(needed: ICMPv6Header.size, available: bytesReceived) + } + + var offset = 0 + var header = ICMPv6Header() + offset = try header.bindBuffer(&buffer, offset: offset) + + return (sourceAddr: ipAddr, header: header, bytes: buffer, length: bytesReceived, offset: offset) + } +} + +extension ICMPv4Session { + /// Receive and wait for a specific message type + public func recv(type: ICMPv4MessageType, timeout: Duration = .seconds(5)) throws -> (sourceAddr: IPv4Address, header: ICMPv4Header, bytes: [UInt8], length: Int, offset: Int) { + let deadline = Date.now + timeout / .seconds(1) + while Date.now < deadline { + let (addr, header, buffer, length, offset) = try recvHeader() + if header.type == type { + return (sourceAddr: addr, header: header, bytes: buffer, length: length, offset: offset) + } + } + throw ICMPError.timeout + } +} + +extension ICMPv6Session { + /// Receive and wait for a specific message type + public func recv(type: ICMPv6MessageType, timeout: Duration = .seconds(5)) throws -> (sourceAddr: IPv6Address, header: ICMPv6Header, bytes: [UInt8], length: Int, offset: Int) { + let deadline = Date.now + timeout / .seconds(1) + while Date.now < deadline { + let (addr, header, buffer, length, offset) = try recvHeader() + if header.type == type { + return (sourceAddr: addr, header: header, bytes: buffer, length: length, offset: offset) + } + } + throw ICMPError.timeout + } +} diff --git a/Sources/ContainerizationICMP/ICMPSocket.swift b/Sources/ContainerizationICMP/ICMPSocket.swift new file mode 100644 index 00000000..63df096f --- /dev/null +++ b/Sources/ContainerizationICMP/ICMPSocket.swift @@ -0,0 +1,215 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras +import Foundation +import Synchronization + +#if canImport(Musl) +import Musl +let osClose = Musl.close +let osSocket = Musl.socket +let osSockRaw = Int32(SOCK_RAW) +#elseif canImport(Glibc) +import Glibc +let osClose = Glibc.close +let osSocket = Glibc.socket +let osSockRaw = Int32(SOCK_RAW.rawValue) +#elseif canImport(Darwin) +import Darwin +let osClose = Darwin.close +let osSocket = Darwin.socket +let osSockRaw = SOCK_RAW +#else +#error("Platform not supported.") +#endif + +public final class ICMPv4Socket: Sendable { + private let sockfd: Mutex + + public init() throws { + let fd = osSocket(AF_INET, osSockRaw, Int32(IPPROTO_ICMP)) + guard fd >= 0 else { + let err = errno + if err == EPERM { + throw ICMPSocketError.permissionDenied + } + throw ICMPSocketError.openFailed(errno: err) + } + sockfd = .init(fd) + } + + deinit { + try? close() + } + + public func close() throws { + try sockfd.withLock { fd in + guard osClose(fd) >= 0 else { + throw ICMPSocketError.closeFailed(errno: errno) + } + } + } + + public func send(buffer: [UInt8], to ipAddr: IPv4Address) throws -> Int { + guard let addr = sockaddr_in(address: ipAddr) else { + throw ICMPSocketError.invalidAddress(address: ipAddr.description) + } + let bufferLen = buffer.count + let addrLen = socklen_t(MemoryLayout.size) + let count = buffer.withUnsafeBytes { bufPtr in + withUnsafePointer(to: addr) { addrPtr in + addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + sockfd.withLock { fd in + sendto(fd, bufPtr.baseAddress, bufferLen, 0, sockaddrPtr, addrLen) + } + } + } + } + + guard count >= 0 else { + throw ICMPSocketError.sendFailed(errno: errno) + } + + return count + } + + public func receive(buffer: inout [UInt8]) throws -> (Int, IPv4Address) { + var addr = sockaddr_in() + var addrLen = socklen_t(MemoryLayout.size) + let bufferLen = buffer.count + let count = buffer.withUnsafeMutableBytes { bufPtr in + withUnsafeMutablePointer(to: &addr) { addrPtr in + addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + sockfd.withLock { fd in + recvfrom(fd, bufPtr.baseAddress, bufferLen, 0, sockaddrPtr, &addrLen) + } + } + } + } + + guard count >= 0 else { + throw ICMPSocketError.receiveFailed(errno: errno) + } + + return (count, try addr.toIPv4Address()) + } +} + +public final class ICMPv6Socket: Sendable { + private let sockfd: Mutex + + public init() throws { + let fd = osSocket(AF_INET6, osSockRaw, Int32(IPPROTO_ICMPV6)) + guard fd >= 0 else { + let err = errno + if err == EPERM { + throw ICMPSocketError.permissionDenied + } + throw ICMPSocketError.openFailed(errno: err) + } + + // Set hop limit to 255 for multicast (required for Router Solicitation per RFC 4861) + var hops: Int32 = 255 + setsockopt(fd, Int32(IPPROTO_IPV6), IPV6_MULTICAST_HOPS, &hops, socklen_t(MemoryLayout.size)) + + sockfd = .init(fd) + } + + deinit { + try? close() + } + + public func close() throws { + try sockfd.withLock { fd in + guard osClose(fd) >= 0 else { + throw ICMPSocketError.closeFailed(errno: errno) + } + } + } + + public func send(buffer: [UInt8], to ipAddr: IPv6Address) throws -> Int { + let addr = try sockaddr_in6(address: ipAddr) + let addrLen = socklen_t(MemoryLayout.size) + let bufferLen = buffer.count + let count = buffer.withUnsafeBytes { bufPtr in + withUnsafePointer(to: addr) { addrPtr in + addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + sockfd.withLock { fd in + #if os(Darwin) + if ipAddr.isMulticast { + var scopeId = try ipAddr.scopeId() + setsockopt(fd, Int32(IPPROTO_IPV6), osIPv6MulticastIf, &scopeId, socklen_t(MemoryLayout.size)) + } + #endif + sendto(fd, bufPtr.baseAddress, bufferLen, 0, sockaddrPtr, addrLen) + } + } + } + } + + guard count >= 0 else { + throw ICMPSocketError.sendFailed(errno: errno) + } + + return count + } + + public func receive(buffer: inout [UInt8]) throws -> (Int, IPv6Address) { + var addr = sockaddr_in6() + var addrLen = socklen_t(MemoryLayout.size) + let bufferLen = buffer.count + let count = buffer.withUnsafeMutableBytes { bufPtr in + withUnsafeMutablePointer(to: &addr) { addrPtr in + addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in + sockfd.withLock { fd in + recvfrom( + fd, bufPtr.baseAddress, bufferLen, 0, + sockaddrPtr, &addrLen) + } + } + } + } + + guard count >= 0 else { + throw ICMPSocketError.receiveFailed(errno: errno) + } + + return (count, try addr.toIPv6Address()) + } +} + +struct icmp6_filter { + var data: (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32, UInt32) = (0, 0, 0, 0, 0, 0, 0, 0) +} + +extension sockaddr_in { + init?(address: IPv4Address) { + self.init() + self.sin_family = sa_family_t(AF_INET) + self.sin_port = 0 + withUnsafeMutableBytes(of: &self.sin_addr) { ptr in + ptr.copyBytes(from: address.bytes) + } + } + + func toIPv4Address() throws -> IPv4Address { + let bytes: [UInt8] = withUnsafeBytes(of: sin_addr) { ptr in + [UInt8](ptr) // Using bracket notation + } + return try IPv4Address(bytes) + } +} diff --git a/Sources/ContainerizationICMP/ICMPSocketError.swift b/Sources/ContainerizationICMP/ICMPSocketError.swift new file mode 100644 index 00000000..321dd512 --- /dev/null +++ b/Sources/ContainerizationICMP/ICMPSocketError.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras + +/// Errors thrown when interacting with an ICMP socket. +public enum ICMPSocketError: Error, CustomStringConvertible, Equatable { + case openFailed(errno: Int32) + case closeFailed(errno: Int32) + case bindFailed(errno: Int32) + case sendFailed(errno: Int32) + case receiveFailed(errno: Int32) + case invalidAddress(address: String) + case permissionDenied + case notImplemented + + public var description: String { + switch self { + case .openFailed(let errno): + return "could not create ICMP socket, errno = \(errno)" + case .closeFailed(let errno): + return "could not create ICMP socket, errno = \(errno)" + case .bindFailed(let errno): + return "could not bind ICMP socket, errno = \(errno)" + case .sendFailed(let errno): + return "could not send ICMP packet, errno = \(errno)" + case .receiveFailed(let errno): + return "could not receive ICMP packet, errno = \(errno)" + case .invalidAddress(let address): + return "invalid address \(address)" + case .permissionDenied: + return "permission denied - raw sockets require root/CAP_NET_RAW" + case .notImplemented: + return "socket function not implemented for platform" + } + } +} diff --git a/Sources/ContainerizationICMP/NDOption.swift b/Sources/ContainerizationICMP/NDOption.swift new file mode 100644 index 00000000..83b200f9 --- /dev/null +++ b/Sources/ContainerizationICMP/NDOption.swift @@ -0,0 +1,492 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras +import Foundation + +/// Neighbor Discovery option types as defined in RFC 4861 and subsequent RFCs +public enum NDOptionType: UInt8, Sendable { + case sourceLinkLayerAddress = 1 // RFC 4861 + case targetLinkLayerAddress = 2 // RFC 4861 + case prefixInformation = 3 // RFC 4861 + case redirectedHeader = 4 // RFC 4861 + case mtu = 5 // RFC 4861 + case routeInformation = 24 // RFC 4191 + case recursiveDNSServer = 25 // RFC 8106 + case dnsSearchList = 31 // RFC 8106 + case captivePortal = 37 // RFC 7710 +} + +public struct NDOptionHeader: Bindable { + public static let size: Int = 2 + + public var type: NDOptionType + + public var lengthInUnits: UInt8 + + public init(type: NDOptionType, lengthInUnits: UInt8) { + self.type = type + self.lengthInUnits = lengthInUnits + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(as: UInt8.self, value: type.rawValue, offset: offset) else { + throw BindError.sendMarshalFailure(type: "NDOptionHeader", field: "type") + } + + guard let offset = buffer.copyIn(as: UInt8.self, value: lengthInUnits, offset: offset) else { + throw BindError.sendMarshalFailure(type: "NDOptionHeader", field: "lengthInUnits") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: NDOption tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "NDOptionHeader", field: "type") + } + guard let value = NDOptionType(rawValue: value) else { + throw BindError.recvMarshalFailure(type: "NDOptionHeader", field: "type") + } + type = value + + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "NDOptionHeader", field: "lengthInUnits") + } + lengthInUnits = value + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: NDOption rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +public struct SourceLinkLayerAddress: Bindable { + public static let size: Int = 6 + + public var address: MACAddress + + public init(address: MACAddress) { + self.address = address + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(buffer: address.bytes, offset: offset) else { + throw BindError.sendMarshalFailure(type: "SourceLinkLayerAddress", field: "address") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: source link layer address tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + var bytes = [UInt8](repeating: 0, count: 6) + guard let offset = buffer.copyOut(buffer: &bytes, offset: offset) else { + throw BindError.recvMarshalFailure(type: "SourceLinkLayerAddress", field: "bytes[0..<6]") + } + address = try MACAddress(bytes) + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: source link layer address rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +public struct PrefixInformation: Bindable { + public static let size: Int = 30 + + public var prefixLength: UInt8 + public var onLinkFlag: Bool + public var autonomousFlag: Bool + public var validLifetime: UInt32 + public var preferredLifetime: UInt32 + public var prefix: IPv6Address + + public init( + prefixLength: UInt8, + onLinkFlag: Bool, + autonomousFlag: Bool, + validLifetime: UInt32, + preferredLifetime: UInt32, + prefix: IPv6Address + ) { + self.prefixLength = prefixLength + self.onLinkFlag = onLinkFlag + self.autonomousFlag = autonomousFlag + self.validLifetime = validLifetime + self.preferredLifetime = preferredLifetime + self.prefix = prefix + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + guard let offset = buffer.copyIn(as: UInt8.self, value: prefixLength, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "prefixLength") + } + + let flags = UInt8((onLinkFlag ? 0x80 : 0x00) | (autonomousFlag ? 0x40 : 0x00)) + guard let offset = buffer.copyIn(as: UInt8.self, value: flags, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "flags") + } + + guard let offset = buffer.copyIn(as: UInt32.self, value: validLifetime.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "validLifetime") + } + + guard let offset = buffer.copyIn(as: UInt32.self, value: preferredLifetime.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "preferredLifetime") + } + + guard let offset = buffer.copyIn(as: UInt32.self, value: 0, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "reserved") + } + + guard let offset = buffer.copyIn(buffer: prefix.bytes, offset: offset) else { + throw BindError.sendMarshalFailure(type: "PrefixInformation", field: "prefix") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: prefix information tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "prefixLength") + } + prefixLength = value + + guard let (offset, flags) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "flags") + } + onLinkFlag = (flags & 0x80) != 0 + autonomousFlag = (flags & 0x40) != 0 + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "validLifetime") + } + validLifetime = UInt32(bigEndian: value) + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "preferredLifetime") + } + preferredLifetime = UInt32(bigEndian: value) + + // Skip reserved field + guard let (offset, _) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "reserved") + } + + var prefixBytes = [UInt8](repeating: 0, count: 16) + guard let offset = buffer.copyOut(buffer: &prefixBytes, offset: offset) else { + throw BindError.recvMarshalFailure(type: "PrefixInformation", field: "bytes[0..<16]") + } + prefix = try IPv6Address(prefixBytes) + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: prefix information rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +public struct MTUOption: Bindable { + public static let size: Int = 6 + + public var mtu: UInt32 + + public init(mtu: UInt32) { + self.mtu = mtu + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + guard let offset = buffer.copyIn(as: UInt16.self, value: 0, offset: offset) else { + throw BindError.sendMarshalFailure(type: "MTUOption", field: "reserved") + } + + guard let offset = buffer.copyIn(as: UInt32.self, value: mtu.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "MTUOption", field: "mtu") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: MTU option tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + // Skip reserved field + guard let (offset, _) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "MTUOption", field: "reserved") + } + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "MTUOption", field: "mtu") + } + mtu = UInt32(bigEndian: value) + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: MTU option rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +public struct RecursiveDNSServer: Bindable { + public static let size: Int = 6 + public var lifetime: UInt32 + + public init(lifetime: UInt32) { + self.lifetime = lifetime + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + guard let offset = buffer.copyIn(as: UInt16.self, value: 0, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RecursiveDNSServer", field: "reserved") + } + + guard let offset = buffer.copyIn(as: UInt32.self, value: lifetime.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RecursiveDNSServer", field: "lifetime") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: recursive DNS server option tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + // Skip reserved field + guard let (offset, _) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RecursiveDNSServer", field: "reserved") + } + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RecursiveDNSServer", field: "lifetime") + } + lifetime = UInt32(bigEndian: value) + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: recursive DNS server option rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +public struct IPv6AddressOptionData: Bindable { + public static let size: Int = 16 + + public var address: IPv6Address + + public init(address: IPv6Address) { + self.address = address + } + + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let offset = buffer.copyIn(buffer: address.bytes, offset: offset) else { + throw BindError.sendMarshalFailure(type: "IPv6AddressOptionData", field: "address") + } + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: IPv6 address tx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + + var bytes = [UInt8](repeating: 0, count: 16) + guard let offset = buffer.copyOut(buffer: &bytes, offset: offset) else { + throw BindError.recvMarshalFailure(type: "IPv6AddressOptionData", field: "bytes[0..<16]") + } + address = try IPv6Address(bytes) + + let actualSize = offset - startOffset + assert(actualSize == Self.size, "BUG: IPv6 address rx length mismatch - expected \(Self.size), got \(actualSize)") + return offset + } +} + +// MARK: - NDOption Enum + +public enum NDOption: Sendable { + case sourceLinkLayerAddress(MACAddress) + case prefixInformation(PrefixInformation) + case mtu(UInt32) + case recursiveDNSServer(lifetime: UInt32, addresses: [IPv6Address]) + + /// Get the option type + public var type: NDOptionType { + switch self { + case .sourceLinkLayerAddress: return .sourceLinkLayerAddress + case .prefixInformation: return .prefixInformation + case .mtu: return .mtu + case .recursiveDNSServer: return .recursiveDNSServer + } + } + + /// Calculate length in 8-byte units (including 2-byte header) + public var lengthInUnits: UInt8 { + switch self { + case .sourceLinkLayerAddress: + return 1 // 2 (header) + 6 (MAC) = 8 bytes + case .prefixInformation: + return 4 // 2 (header) + 30 (data) = 32 bytes + case .mtu: + return 1 // 2 (header) + 6 (data) = 8 bytes + case .recursiveDNSServer(_, let addresses): + let totalBytes = 2 + 6 + (addresses.count * 16) + return UInt8(totalBytes / 8) + } + } + + /// Serialize option to buffer (header + payload) + public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + var currentOffset = offset + + // Write header + let header = NDOptionHeader(type: type, lengthInUnits: lengthInUnits) + currentOffset = try header.appendBuffer(&buffer, offset: currentOffset) + + // Write payload based on type + switch self { + case .sourceLinkLayerAddress(let mac): + let payload = SourceLinkLayerAddress(address: mac) + currentOffset = try payload.appendBuffer(&buffer, offset: currentOffset) + + case .prefixInformation(let prefix): + currentOffset = try prefix.appendBuffer(&buffer, offset: currentOffset) + + case .mtu(let mtu): + let payload = MTUOption(mtu: mtu) + currentOffset = try payload.appendBuffer(&buffer, offset: currentOffset) + + case .recursiveDNSServer(let lifetime, let addresses): + let rdnss = RecursiveDNSServer(lifetime: lifetime) + currentOffset = try rdnss.appendBuffer(&buffer, offset: currentOffset) + for address in addresses { + let addrData = IPv6AddressOptionData(address: address) + currentOffset = try addrData.appendBuffer(&buffer, offset: currentOffset) + } + } + + return currentOffset + } +} + +// MARK: - Option Parsing + +extension Array where Element == UInt8 { + /// Parse all Neighbor Discovery options from this buffer + /// - Parameters: + /// - offset: Starting offset in the buffer + /// - length: Total length of options data in bytes + /// - Returns: Array of parsed NDOption values + /// - Throws: BindError if parsing fails + public mutating func parseNDOptions(offset: Int, length: Int) throws -> [NDOption] { + var options: [NDOption] = [] + var currentOffset = offset + let endOffset = offset + length + + while currentOffset < endOffset { + // Read header fields manually to handle unknown types + guard let (typeOffset, typeValue) = self.copyOut(as: UInt8.self, offset: currentOffset) else { + throw BindError.recvMarshalFailure(type: "NDOption", field: "type") + } + + guard let (lengthOffset, lengthInUnits) = self.copyOut(as: UInt8.self, offset: typeOffset) else { + throw BindError.recvMarshalFailure(type: "NDOption", field: "lengthInUnits") + } + + guard lengthInUnits > 0 else { + throw BindError.recvMarshalFailure(type: "NDOption", field: "lengthInUnits") + } + + currentOffset = lengthOffset + let payloadLength = Int(lengthInUnits) * 8 - NDOptionHeader.size + + // Check if this is a known option type + guard let optionType = NDOptionType(rawValue: typeValue) else { + // Unknown option type - skip it + print("Warning: skipping unknown ND option type \(typeValue)") + currentOffset += payloadLength + continue + } + + // Parse payload based on type + let option: NDOption + switch optionType { + case .sourceLinkLayerAddress: + var payload = SourceLinkLayerAddress(address: MACAddress(0)) + currentOffset = try payload.bindBuffer(&self, offset: currentOffset) + option = .sourceLinkLayerAddress(payload.address) + + case .prefixInformation: + var payload = PrefixInformation( + prefixLength: 0, onLinkFlag: false, autonomousFlag: false, + validLifetime: 0, preferredLifetime: 0, prefix: IPv6Address(0) + ) + currentOffset = try payload.bindBuffer(&self, offset: currentOffset) + option = .prefixInformation(payload) + + case .mtu: + var payload = MTUOption(mtu: 0) + currentOffset = try payload.bindBuffer(&self, offset: currentOffset) + option = .mtu(payload.mtu) + + case .recursiveDNSServer: + var rdnss = RecursiveDNSServer(lifetime: 0) + currentOffset = try rdnss.bindBuffer(&self, offset: currentOffset) + + // Parse remaining addresses + let remainingBytes = payloadLength - RecursiveDNSServer.size + let addressCount = remainingBytes / 16 + var addresses: [IPv6Address] = [] + for _ in 0.. Int { + let startOffset = offset + guard let offset = buffer.copyIn(as: UInt8.self, value: currentHopLimit, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterAdvertisement", field: "currentHopLimit") + } + let autoconfig = UInt8((managedFlag ? 0x80 : 0x00) | (otherFlag ? 0x40 : 0x00)) + guard let offset = buffer.copyIn(as: UInt8.self, value: autoconfig, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterAdvertisement", field: "flags") + } + guard let offset = buffer.copyIn(as: UInt16.self, value: routerLifetime.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterAdvertisement", field: "routerLifetime") + } + guard let offset = buffer.copyIn(as: UInt32.self, value: reachableTime.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterAdvertisement", field: "reachableTime") + } + guard let offset = buffer.copyIn(as: UInt32.self, value: retransTimer.bigEndian, offset: offset) else { + throw BindError.sendMarshalFailure(type: "RouterAdvertisement", field: "retransTimer") + } + + assert(offset - startOffset == Self.size, "BUG: router advertisement appendBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } + + public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int { + let startOffset = offset + guard let (offset, value) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RouterAdvertisement", field: "currentHopLimit") + } + currentHopLimit = value + + guard let (offset, autoconfig) = buffer.copyOut(as: UInt8.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RouterAdvertisement", field: "flags") + } + managedFlag = (autoconfig & 0x80) != 0 + otherFlag = (autoconfig & 0x40) != 0 + + guard let (offset, value) = buffer.copyOut(as: UInt16.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RouterAdvertisement", field: "routerLifetime") + } + routerLifetime = UInt16(bigEndian: value) + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RouterAdvertisement", field: "reachableTime") + } + reachableTime = UInt32(bigEndian: value) + + guard let (offset, value) = buffer.copyOut(as: UInt32.self, offset: offset) else { + throw BindError.recvMarshalFailure(type: "RouterAdvertisement", field: "retransTimer") + } + retransTimer = UInt32(bigEndian: value) + + assert(offset - startOffset == Self.size, "BUG: router advertisement bindBuffer length mismatch - expected \(Self.size), got \(offset - startOffset)") + return offset + } +} diff --git a/Tests/ContainerizationICMPTests/EchoTests.swift b/Tests/ContainerizationICMPTests/EchoTests.swift new file mode 100644 index 00000000..a281ddd5 --- /dev/null +++ b/Tests/ContainerizationICMPTests/EchoTests.swift @@ -0,0 +1,216 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import Testing + +@testable import ContainerizationICMP + +// Tests based on RFC 792 (ICMPv4) and RFC 4443 (ICMPv6) Echo Request/Reply +struct EchoTests { + + // MARK: - Echo Header Tests (RFC 792, RFC 4443) + + @Test + func testEchoRoundtrip() throws { + let echo = try Echo(identifier: 0x1234, sequenceNumber: 0x5678) + + var buffer = [UInt8](repeating: 0, count: Echo.size) + let bytesWritten = try echo.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == Echo.size) + + // Verify wire format (Big Endian) + // Identifier 0x1234 -> 0x12, 0x34 + #expect(buffer[0] == 0x12) + #expect(buffer[1] == 0x34) + // Sequence 0x5678 -> 0x56, 0x78 + #expect(buffer[2] == 0x56) + #expect(buffer[3] == 0x78) + + var parsedEcho = try Echo(identifier: 0, sequenceNumber: 0) + let bytesRead = try parsedEcho.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == Echo.size) + #expect(parsedEcho.identifier == 0x1234) + #expect(parsedEcho.sequenceNumber == 0x5678) + } + + @Test + func testEchoPayloadRTTRoundtrip() throws { + let now = Date() + let payload = EchoPayloadRTT(date: now) + + var buffer = [UInt8](repeating: 0, count: EchoPayloadRTT.size) + let bytesWritten = try payload.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == EchoPayloadRTT.size) + + var parsedPayload = EchoPayloadRTT() + let bytesRead = try parsedPayload.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == EchoPayloadRTT.size) + // Date comparison might need tolerance due to Double precision in TimeInterval + #expect(abs(parsedPayload.date.timeIntervalSinceReferenceDate - now.timeIntervalSinceReferenceDate) < 0.001) + } + + @Test + func testEchoPayloadRTTCalculation() { + let past = Date(timeIntervalSinceReferenceDate: 1000) + let payload = EchoPayloadRTT(date: past) + + let future = Date(timeIntervalSinceReferenceDate: 1001.5) + let rtt = payload.rtt(atDate: future) + + #expect(rtt == 1.5) + } + + // MARK: - Process ID as Identifier (RFC 792 Common Practice) + + @Test + func testEchoWithProcessID() throws { + // RFC 792: Identifier is often set to process ID + let processID = UInt16(truncatingIfNeeded: 12345) + let echo = try Echo(identifier: processID, sequenceNumber: 1) + + var buffer = [UInt8](repeating: 0, count: Echo.size) + _ = try echo.appendBuffer(&buffer, offset: 0) + + var parsedEcho = try Echo(identifier: 0, sequenceNumber: 0) + _ = try parsedEcho.bindBuffer(&buffer, offset: 0) + + #expect(parsedEcho.identifier == processID) + #expect(parsedEcho.sequenceNumber == 1) + } + + @Test + func testEchoSequenceIncrement() throws { + // RFC 792: Sequence numbers typically increment for each echo + var echoes: [Echo] = [] + for seq in 1...5 { + let echo = try Echo(identifier: 100, sequenceNumber: UInt16(seq)) + echoes.append(echo) + } + + #expect(echoes[0].sequenceNumber == 1) + #expect(echoes[4].sequenceNumber == 5) + } + + // MARK: - Echo Payload Edge Cases + + @Test + func testEchoPayloadRTTWithFutureDate() { + // Test RTT with future date + let future = Date(timeIntervalSinceReferenceDate: 2000) + let payload = EchoPayloadRTT(date: future) + + let past = Date(timeIntervalSinceReferenceDate: 1500) + let rtt = payload.rtt(atDate: past) + + // RTT should be negative when "now" is before send time + #expect(rtt == -500.0) + } + + @Test + func testEchoPayloadRTTDefaultDate() { + // Test that default constructor uses current time + let before = Date() + let payload = EchoPayloadRTT() + let after = Date() + + // Payload date should be between before and after + #expect(payload.date >= before) + #expect(payload.date <= after) + } + + @Test + func testEchoPayloadRTTDefaultCalculation() { + // Test RTT calculation with default (current) date + let past = Date(timeIntervalSinceReferenceDate: Date().timeIntervalSinceReferenceDate - 1.0) + let payload = EchoPayloadRTT(date: past) + + let rtt = payload.rtt() // Uses Date() internally + + // RTT should be approximately 1 second (with some tolerance for execution time) + #expect(rtt >= 1.0) + #expect(rtt <= 1.1) + } + + // MARK: - Wire Format Tests + + @Test + func testEchoZeroValues() throws { + // RFC 792: Test with all zero values + let echo = try Echo(identifier: 0, sequenceNumber: 0) + + var buffer = [UInt8](repeating: 0, count: Echo.size) + let bytesWritten = try echo.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == Echo.size) + #expect(buffer[0] == 0) + #expect(buffer[1] == 0) + #expect(buffer[2] == 0) + #expect(buffer[3] == 0) + } + + @Test + func testEchoMaxValues() throws { + // RFC 792: Test with maximum values + let echo = try Echo(identifier: 0xFFFF, sequenceNumber: 0xFFFF) + + var buffer = [UInt8](repeating: 0, count: Echo.size) + _ = try echo.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 0xFF) + #expect(buffer[1] == 0xFF) + #expect(buffer[2] == 0xFF) + #expect(buffer[3] == 0xFF) + + var parsedEcho = try Echo(identifier: 0, sequenceNumber: 0) + _ = try parsedEcho.bindBuffer(&buffer, offset: 0) + + #expect(parsedEcho.identifier == 0xFFFF) + #expect(parsedEcho.sequenceNumber == 0xFFFF) + } + + @Test + func testEchoPayloadSize() { + // RFC 792: Verify payload is standard 56 bytes + #expect(EchoPayloadRTT.size == 56) + } + + @Test + func testEchoHeaderSize() { + // RFC 792/4443: Echo header is 4 bytes (identifier + sequence) + #expect(Echo.size == 4) + } + + @Test + func testEchoPayloadRTTPrecision() throws { + // Test that timestamp preserves microsecond precision + let preciseTime = Date(timeIntervalSinceReferenceDate: 1234567.123456) + let payload = EchoPayloadRTT(date: preciseTime) + + var buffer = [UInt8](repeating: 0, count: EchoPayloadRTT.size) + _ = try payload.appendBuffer(&buffer, offset: 0) + + var parsedPayload = EchoPayloadRTT() + _ = try parsedPayload.bindBuffer(&buffer, offset: 0) + + // Should preserve precision within Double's limits + #expect(abs(parsedPayload.date.timeIntervalSinceReferenceDate - preciseTime.timeIntervalSinceReferenceDate) < 0.000001) + } +} diff --git a/Tests/ContainerizationICMPTests/ICMPMessageTests.swift b/Tests/ContainerizationICMPTests/ICMPMessageTests.swift new file mode 100644 index 00000000..014ce307 --- /dev/null +++ b/Tests/ContainerizationICMPTests/ICMPMessageTests.swift @@ -0,0 +1,265 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Testing + +@testable import ContainerizationICMP + +// Tests based on RFC 792 (ICMPv4) and RFC 4443 (ICMPv6) +struct ICMPMessageTests { + + // MARK: - ICMPv4 Tests (RFC 792) + + @Test + func testICMPv4HeaderRoundtrip() throws { + let header = ICMPv4Header(type: .echoRequest, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv4Header.size) + let bytesWritten = try header.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == ICMPv4Header.size) + + // Verify wire format + // Type: 8 (Echo Request), Code: 0, Checksum: 0 (placeholder) + #expect(buffer[0] == 8) + #expect(buffer[1] == 0) + #expect(buffer[2] == 0) + #expect(buffer[3] == 0) + + var parsedHeader = ICMPv4Header() + let bytesRead = try parsedHeader.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == ICMPv4Header.size) + #expect(parsedHeader.type == .echoRequest) + #expect(parsedHeader.code == 0) + } + + @Test + func testICMPv4Checksum() { + // Example buffer: [Type, Code, Checksum, Checksum, ID, ID, Seq, Seq] + // 8, 0, 0, 0, 1, 2, 3, 4 + // Words: 0x0800, 0x0000, 0x0102, 0x0304 + // Sum: 0x0800 + 0x0000 + 0x0102 + 0x0304 = 0x0C06 + // One's complement: ~0x0C06 = 0xF3F9 + + let buffer: [UInt8] = [8, 0, 0, 0, 1, 2, 3, 4] + let checksum = ICMPv4Header.checksum(buffer: buffer, offset: 0, length: buffer.count) + + #expect(checksum == 0xF3F9) + } + + @Test + func testICMPv4Match() { + let header = ICMPv4Header(type: .destinationUnreachable, code: 3) + + #expect(header.matches(type: .destinationUnreachable, code: 3)) + #expect(!header.matches(type: .destinationUnreachable, code: 1)) + #expect(!header.matches(type: .echoReply, code: 3)) + } + + // MARK: - IPv6 Tests + + @Test + func testICMPv6HeaderRoundtrip() throws { + let header = ICMPv6Header(type: .echoRequest, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + let bytesWritten = try header.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == ICMPv6Header.size) + + // Verify wire format + // Type: 128 (Echo Request), Code: 0, Checksum: 0 (placeholder) + #expect(buffer[0] == 128) + #expect(buffer[1] == 0) + #expect(buffer[2] == 0) + #expect(buffer[3] == 0) + + var parsedHeader = ICMPv6Header() + let bytesRead = try parsedHeader.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == ICMPv6Header.size) + #expect(parsedHeader.type == .echoRequest) + #expect(parsedHeader.code == 0) + } + + @Test + func testICMPv6Match() { + let header = ICMPv6Header(type: .neighborAdvertisement, code: 0) + + #expect(header.matches(type: .neighborAdvertisement, code: 0)) + #expect(!header.matches(type: .neighborSolicitation, code: 0)) + } + + // MARK: - Additional ICMPv4 Message Types (RFC 792) + + @Test + func testICMPv4EchoReply() throws { + // RFC 792: Type 0 - Echo Reply + let header = ICMPv4Header(type: .echoReply, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv4Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 0) + #expect(buffer[1] == 0) + } + + @Test + func testICMPv4DestinationUnreachable() throws { + // RFC 792: Type 3 - Destination Unreachable, Code 3 - Port Unreachable + let header = ICMPv4Header(type: .destinationUnreachable, code: 3) + + var buffer = [UInt8](repeating: 0, count: ICMPv4Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 3) + #expect(buffer[1] == 3) + + var parsedHeader = ICMPv4Header() + _ = try parsedHeader.bindBuffer(&buffer, offset: 0) + + #expect(parsedHeader.type == .destinationUnreachable) + #expect(parsedHeader.code == 3) + } + + @Test + func testICMPv4TimeExceeded() throws { + // RFC 792: Type 11 - Time Exceeded, Code 0 - TTL exceeded in transit + let header = ICMPv4Header(type: .timeExceeded, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv4Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 11) + #expect(buffer[1] == 0) + } + + @Test + func testICMPv4Redirect() throws { + // RFC 792: Type 5 - Redirect, Code 1 - Redirect for Host + let header = ICMPv4Header(type: .redirect, code: 1) + + var buffer = [UInt8](repeating: 0, count: ICMPv4Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 5) + #expect(buffer[1] == 1) + } + + // MARK: - Additional ICMPv6 Message Types (RFC 4443) + + @Test + func testICMPv6EchoReply() throws { + // RFC 4443: Type 129 - Echo Reply + let header = ICMPv6Header(type: .echoReply, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 129) + #expect(buffer[1] == 0) + } + + @Test + func testICMPv6RouterSolicitation() throws { + // RFC 4861: Type 133 - Router Solicitation + let header = ICMPv6Header(type: .routerSolicitation, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 133) + #expect(buffer[1] == 0) + + var parsedHeader = ICMPv6Header() + _ = try parsedHeader.bindBuffer(&buffer, offset: 0) + + #expect(parsedHeader.type == .routerSolicitation) + #expect(parsedHeader.code == 0) + } + + @Test + func testICMPv6RouterAdvertisement() throws { + // RFC 4861: Type 134 - Router Advertisement + let header = ICMPv6Header(type: .routerAdvertisement, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 134) + #expect(buffer[1] == 0) + } + + @Test + func testICMPv6NeighborSolicitation() throws { + // RFC 4861: Type 135 - Neighbor Solicitation + let header = ICMPv6Header(type: .neighborSolicitation, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 135) + #expect(buffer[1] == 0) + } + + @Test + func testICMPv6NeighborAdvertisement() throws { + // RFC 4861: Type 136 - Neighbor Advertisement + let header = ICMPv6Header(type: .neighborAdvertisement, code: 0) + + var buffer = [UInt8](repeating: 0, count: ICMPv6Header.size) + _ = try header.appendBuffer(&buffer, offset: 0) + + #expect(buffer[0] == 136) + #expect(buffer[1] == 0) + } + + // MARK: - Checksum Edge Cases + + @Test + func testICMPv4ChecksumAllZeros() { + // RFC 792: Checksum of all zeros + let buffer: [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0] + let checksum = ICMPv4Header.checksum(buffer: buffer, offset: 0, length: buffer.count) + + #expect(checksum == 0xFFFF) + } + + @Test + func testICMPv4ChecksumOddLength() { + // RFC 792: Checksum with odd length (last byte padded) + // Buffer: [1, 2, 3] -> words: 0x0102, 0x0300 + // Sum: 0x0102 + 0x0300 = 0x0402 + // Complement: ~0x0402 = 0xFBFD + let buffer: [UInt8] = [1, 2, 3] + let checksum = ICMPv4Header.checksum(buffer: buffer, offset: 0, length: buffer.count) + + #expect(checksum == 0xFBFD) + } + + @Test + func testICMPv4ChecksumWithCarry() { + // RFC 792: Test carry during checksum calculation + // Words that will generate carries + let buffer: [UInt8] = [0xFF, 0xFF, 0x00, 0x01] + let checksum = ICMPv4Header.checksum(buffer: buffer, offset: 0, length: buffer.count) + + // 0xFFFF + 0x0001 = 0x10000 -> fold to 0x0000 + 0x0001 = 0x0001 + // Complement: ~0x0001 = 0xFFFE + #expect(checksum == 0xFFFE) + } +} diff --git a/Tests/ContainerizationICMPTests/NDOptionTests.swift b/Tests/ContainerizationICMPTests/NDOptionTests.swift new file mode 100644 index 00000000..2184857b --- /dev/null +++ b/Tests/ContainerizationICMPTests/NDOptionTests.swift @@ -0,0 +1,406 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import ContainerizationExtras +import Foundation +import Testing + +@testable import ContainerizationICMP + +// Tests based on RFC 4861 Section 4.6 - Neighbor Discovery Options +struct NDOptionTests { + + // MARK: - Option Header Tests (RFC 4861 Section 4.6) + + @Test + func testNDOptionHeaderRoundtrip() throws { + // RFC 4861: All options have Type and Length fields + let header = NDOptionHeader(type: .sourceLinkLayerAddress, lengthInUnits: 1) + + var buffer = [UInt8](repeating: 0, count: NDOptionHeader.size) + let bytesWritten = try header.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == NDOptionHeader.size) + #expect(NDOptionHeader.size == 2) + + // Verify wire format + // Type: 1 (Source Link-layer Address) + #expect(buffer[0] == 1) + // Length: 1 (in units of 8 octets) + #expect(buffer[1] == 1) + + var parsedHeader = NDOptionHeader(type: .sourceLinkLayerAddress, lengthInUnits: 0) + let bytesRead = try parsedHeader.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == NDOptionHeader.size) + #expect(parsedHeader.type == .sourceLinkLayerAddress) + #expect(parsedHeader.lengthInUnits == 1) + } + + // MARK: - Source Link-Layer Address Option (RFC 4861 Section 4.6.1) + + @Test + func testSourceLinkLayerAddressRoundtrip() throws { + // RFC 4861 Section 4.6.1: Ethernet address is 6 octets + let macAddress = try MACAddress([0x00, 0x11, 0x22, 0x33, 0x44, 0x55]) + let option = SourceLinkLayerAddress(address: macAddress) + + var buffer = [UInt8](repeating: 0, count: SourceLinkLayerAddress.size) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == SourceLinkLayerAddress.size) + #expect(SourceLinkLayerAddress.size == 6) + + // Verify wire format - MAC address bytes + #expect(buffer[0] == 0x00) + #expect(buffer[1] == 0x11) + #expect(buffer[2] == 0x22) + #expect(buffer[3] == 0x33) + #expect(buffer[4] == 0x44) + #expect(buffer[5] == 0x55) + + var parsedOption = SourceLinkLayerAddress(address: MACAddress(0)) + let bytesRead = try parsedOption.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == SourceLinkLayerAddress.size) + #expect(parsedOption.address.bytes == macAddress.bytes) + } + + @Test + func testSourceLinkLayerAddressOptionEnum() throws { + // RFC 4861: Complete option including header + let macAddress = try MACAddress([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]) + let option = NDOption.sourceLinkLayerAddress(macAddress) + + #expect(option.type == .sourceLinkLayerAddress) + #expect(option.lengthInUnits == 1) // 8 bytes total: 2 (header) + 6 (MAC) + + var buffer = [UInt8](repeating: 0, count: 8) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == 8) + + // Verify complete option format + #expect(buffer[0] == 1) // Type + #expect(buffer[1] == 1) // Length + #expect(buffer[2] == 0xAA) // MAC byte 0 + #expect(buffer[3] == 0xBB) // MAC byte 1 + #expect(buffer[4] == 0xCC) // MAC byte 2 + #expect(buffer[5] == 0xDD) // MAC byte 3 + #expect(buffer[6] == 0xEE) // MAC byte 4 + #expect(buffer[7] == 0xFF) // MAC byte 5 + } + + // MARK: - Prefix Information Option (RFC 4861 Section 4.6.2) + + @Test + func testPrefixInformationRoundtrip() throws { + // RFC 4861 Section 4.6.2: Prefix Information for SLAAC + let prefix = try IPv6Address([ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]) + let prefixInfo = PrefixInformation( + prefixLength: 64, + onLinkFlag: true, + autonomousFlag: true, + validLifetime: 2_592_000, // 30 days + preferredLifetime: 604800, // 7 days + prefix: prefix + ) + + var buffer = [UInt8](repeating: 0, count: PrefixInformation.size) + let bytesWritten = try prefixInfo.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == PrefixInformation.size) + #expect(PrefixInformation.size == 30) + + // Verify wire format per RFC 4861 Section 4.6.2 + // Byte 0: Prefix Length + #expect(buffer[0] == 64) + + // Byte 1: Flags (L=1, A=1 -> 0xC0) + #expect(buffer[1] == 0xC0) + + // Bytes 2-5: Valid Lifetime (2592000 = 0x00278D00) + #expect(buffer[2] == 0x00) + #expect(buffer[3] == 0x27) + #expect(buffer[4] == 0x8D) + #expect(buffer[5] == 0x00) + + // Bytes 6-9: Preferred Lifetime (604800 = 0x00093A80) + #expect(buffer[6] == 0x00) + #expect(buffer[7] == 0x09) + #expect(buffer[8] == 0x3A) + #expect(buffer[9] == 0x80) + + // Bytes 10-13: Reserved2 (must be zero) + #expect(buffer[10] == 0x00) + #expect(buffer[11] == 0x00) + #expect(buffer[12] == 0x00) + #expect(buffer[13] == 0x00) + + // Bytes 14-29: Prefix (2001:db8::) + #expect(buffer[14] == 0x20) + #expect(buffer[15] == 0x01) + #expect(buffer[16] == 0x0d) + #expect(buffer[17] == 0xb8) + + var parsedInfo = PrefixInformation( + prefixLength: 0, onLinkFlag: false, autonomousFlag: false, + validLifetime: 0, preferredLifetime: 0, prefix: IPv6Address(0) + ) + let bytesRead = try parsedInfo.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == PrefixInformation.size) + #expect(parsedInfo.prefixLength == 64) + #expect(parsedInfo.onLinkFlag == true) + #expect(parsedInfo.autonomousFlag == true) + #expect(parsedInfo.validLifetime == 2_592_000) + #expect(parsedInfo.preferredLifetime == 604800) + #expect(parsedInfo.prefix.bytes == prefix.bytes) + } + + @Test + func testPrefixInformationOnLinkOnly() throws { + // RFC 4861: On-link prefix without SLAAC (L=1, A=0) + let prefix = try IPv6Address([ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]) + let prefixInfo = PrefixInformation( + prefixLength: 64, + onLinkFlag: true, + autonomousFlag: false, + validLifetime: 0xFFFF_FFFF, + preferredLifetime: 0xFFFF_FFFF, + prefix: prefix + ) + + var buffer = [UInt8](repeating: 0, count: PrefixInformation.size) + _ = try prefixInfo.appendBuffer(&buffer, offset: 0) + + // L=1, A=0 -> 0x80 + #expect(buffer[1] == 0x80) + + var parsedInfo = PrefixInformation( + prefixLength: 0, onLinkFlag: false, autonomousFlag: false, + validLifetime: 0, preferredLifetime: 0, prefix: IPv6Address(0) + ) + _ = try parsedInfo.bindBuffer(&buffer, offset: 0) + + #expect(parsedInfo.onLinkFlag == true) + #expect(parsedInfo.autonomousFlag == false) + } + + @Test + func testPrefixInformationOptionEnum() throws { + // RFC 4861: Complete prefix information option + let prefix = try IPv6Address([ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]) + let prefixInfo = PrefixInformation( + prefixLength: 64, + onLinkFlag: true, + autonomousFlag: true, + validLifetime: 86400, + preferredLifetime: 43200, + prefix: prefix + ) + let option = NDOption.prefixInformation(prefixInfo) + + #expect(option.type == .prefixInformation) + #expect(option.lengthInUnits == 4) // 32 bytes: 2 (header) + 30 (data) + + var buffer = [UInt8](repeating: 0, count: 32) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == 32) + #expect(buffer[0] == 3) // Type: Prefix Information + #expect(buffer[1] == 4) // Length: 4 units + } + + // MARK: - MTU Option (RFC 4861 Section 4.6.4) + + @Test + func testMTUOptionRoundtrip() throws { + // RFC 4861 Section 4.6.4: MTU option for link MTU + let mtuOption = MTUOption(mtu: 1500) + + var buffer = [UInt8](repeating: 0, count: MTUOption.size) + let bytesWritten = try mtuOption.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == MTUOption.size) + #expect(MTUOption.size == 6) + + // Verify wire format + // Bytes 0-1: Reserved (must be zero) + #expect(buffer[0] == 0x00) + #expect(buffer[1] == 0x00) + + // Bytes 2-5: MTU (1500 = 0x000005DC) + #expect(buffer[2] == 0x00) + #expect(buffer[3] == 0x00) + #expect(buffer[4] == 0x05) + #expect(buffer[5] == 0xDC) + + var parsedOption = MTUOption(mtu: 0) + let bytesRead = try parsedOption.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == MTUOption.size) + #expect(parsedOption.mtu == 1500) + } + + @Test + func testMTUOptionEnum() throws { + // RFC 4861: Complete MTU option + let option = NDOption.mtu(9000) // Jumbo frames + + #expect(option.type == .mtu) + #expect(option.lengthInUnits == 1) // 8 bytes: 2 (header) + 6 (data) + + var buffer = [UInt8](repeating: 0, count: 8) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == 8) + #expect(buffer[0] == 5) // Type: MTU + #expect(buffer[1] == 1) // Length: 1 unit + + // MTU value 9000 = 0x00002328 + #expect(buffer[4] == 0x00) + #expect(buffer[5] == 0x00) + #expect(buffer[6] == 0x23) + #expect(buffer[7] == 0x28) + } + + // MARK: - Recursive DNS Server Option (RFC 8106 Section 5.1) + + @Test + func testRecursiveDNSServerRoundtrip() throws { + // RFC 8106: RDNSS option header (without addresses) + let rdnss = RecursiveDNSServer(lifetime: 3600) + + var buffer = [UInt8](repeating: 0, count: RecursiveDNSServer.size) + let bytesWritten = try rdnss.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == RecursiveDNSServer.size) + #expect(RecursiveDNSServer.size == 6) + + // Verify wire format + // Bytes 0-1: Reserved (must be zero) + #expect(buffer[0] == 0x00) + #expect(buffer[1] == 0x00) + + // Bytes 2-5: Lifetime (3600 = 0x00000E10) + #expect(buffer[2] == 0x00) + #expect(buffer[3] == 0x00) + #expect(buffer[4] == 0x0E) + #expect(buffer[5] == 0x10) + + var parsedRDNSS = RecursiveDNSServer(lifetime: 0) + let bytesRead = try parsedRDNSS.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == RecursiveDNSServer.size) + #expect(parsedRDNSS.lifetime == 3600) + } + + @Test + func testRecursiveDNSServerOptionWithSingleAddress() throws { + // RFC 8106: RDNSS with one DNS server + let dnsServer = try IPv6Address([ + 0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, + ]) + let option = NDOption.recursiveDNSServer(lifetime: 7200, addresses: [dnsServer]) + + #expect(option.type == .recursiveDNSServer) + // 24 bytes total: 2 (header) + 6 (RDNSS) + 16 (1 address) = 24 bytes = 3 units + #expect(option.lengthInUnits == 3) + + var buffer = [UInt8](repeating: 0, count: 24) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == 24) + #expect(buffer[0] == 25) // Type: RDNSS + #expect(buffer[1] == 3) // Length: 3 units + + // Lifetime 7200 = 0x00001C20 + #expect(buffer[4] == 0x00) + #expect(buffer[5] == 0x00) + #expect(buffer[6] == 0x1C) + #expect(buffer[7] == 0x20) + + // First address starts at byte 8 + #expect(buffer[8] == 0x20) + #expect(buffer[9] == 0x01) + #expect(buffer[10] == 0x48) + #expect(buffer[11] == 0x60) + } + + @Test + func testRecursiveDNSServerOptionWithMultipleAddresses() throws { + // RFC 8106: RDNSS with two DNS servers + let dns1 = try IPv6Address([ + 0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88, + ]) + let dns2 = try IPv6Address([ + 0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x44, + ]) + let option = NDOption.recursiveDNSServer(lifetime: 3600, addresses: [dns1, dns2]) + + // 40 bytes total: 2 (header) + 6 (RDNSS) + 32 (2 addresses) = 40 bytes = 5 units + #expect(option.lengthInUnits == 5) + + var buffer = [UInt8](repeating: 0, count: 40) + let bytesWritten = try option.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == 40) + #expect(buffer[0] == 25) // Type: RDNSS + #expect(buffer[1] == 5) // Length: 5 units + } + + // MARK: - IPv6 Address Option Data + + @Test + func testIPv6AddressOptionDataRoundtrip() throws { + let address = try IPv6Address([ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x11, 0x22, 0xff, 0xfe, 0x33, 0x44, 0x55, + ]) + let addrData = IPv6AddressOptionData(address: address) + + var buffer = [UInt8](repeating: 0, count: IPv6AddressOptionData.size) + let bytesWritten = try addrData.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == IPv6AddressOptionData.size) + #expect(IPv6AddressOptionData.size == 16) + + // Verify all 16 bytes + #expect(buffer[0] == 0xfe) + #expect(buffer[1] == 0x80) + #expect(buffer[8] == 0x02) + #expect(buffer[15] == 0x55) + + var parsedAddr = IPv6AddressOptionData(address: IPv6Address(0)) + let bytesRead = try parsedAddr.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == IPv6AddressOptionData.size) + #expect(parsedAddr.address.bytes == address.bytes) + } +} diff --git a/Tests/ContainerizationICMPTests/RouterAdvertisementTests.swift b/Tests/ContainerizationICMPTests/RouterAdvertisementTests.swift new file mode 100644 index 00000000..9695c94c --- /dev/null +++ b/Tests/ContainerizationICMPTests/RouterAdvertisementTests.swift @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import Testing + +@testable import ContainerizationICMP + +// Tests based on RFC 4861 Section 4.2 - Router Advertisement Message Format +struct RouterAdvertisementTests { + + @Test + func testRouterAdvertisementRoundtrip() throws { + // RFC 4861: Router Advertisement with typical values + let ra = try RouterAdvertisement( + currentHopLimit: 64, + managedFlag: false, + otherFlag: true, + routerLifetime: 1800, + reachableTime: 30000, + retransTimer: 1000 + ) + + var buffer = [UInt8](repeating: 0, count: RouterAdvertisement.size) + let bytesWritten = try ra.appendBuffer(&buffer, offset: 0) + + #expect(bytesWritten == RouterAdvertisement.size) + #expect(RouterAdvertisement.size == 12) + + // Verify wire format per RFC 4861 Section 4.2 + // Byte 0: Cur Hop Limit + #expect(buffer[0] == 64) + + // Byte 1: M (bit 7) and O (bit 6) flags + // M=0, O=1 -> 0x40 + #expect(buffer[1] == 0x40) + + // Bytes 2-3: Router Lifetime (1800 = 0x0708 in network byte order) + #expect(buffer[2] == 0x07) + #expect(buffer[3] == 0x08) + + // Bytes 4-7: Reachable Time (30000 = 0x00007530 in network byte order) + #expect(buffer[4] == 0x00) + #expect(buffer[5] == 0x00) + #expect(buffer[6] == 0x75) + #expect(buffer[7] == 0x30) + + // Bytes 8-11: Retrans Timer (1000 = 0x000003E8 in network byte order) + #expect(buffer[8] == 0x00) + #expect(buffer[9] == 0x00) + #expect(buffer[10] == 0x03) + #expect(buffer[11] == 0xE8) + + var parsedRA = try RouterAdvertisement() + let bytesRead = try parsedRA.bindBuffer(&buffer, offset: 0) + + #expect(bytesRead == RouterAdvertisement.size) + #expect(parsedRA.currentHopLimit == 64) + #expect(parsedRA.managedFlag == false) + #expect(parsedRA.otherFlag == true) + #expect(parsedRA.routerLifetime == 1800) + #expect(parsedRA.reachableTime == 30000) + #expect(parsedRA.retransTimer == 1000) + } + + @Test + func testRouterAdvertisementWithManagedFlag() throws { + // RFC 4861: M flag indicates addresses available via DHCPv6 + let ra = try RouterAdvertisement( + currentHopLimit: 64, + managedFlag: true, + otherFlag: false, + routerLifetime: 9000, + reachableTime: 0, + retransTimer: 0 + ) + + var buffer = [UInt8](repeating: 0, count: RouterAdvertisement.size) + _ = try ra.appendBuffer(&buffer, offset: 0) + + // M=1, O=0 -> 0x80 + #expect(buffer[1] == 0x80) + + var parsedRA = try RouterAdvertisement() + _ = try parsedRA.bindBuffer(&buffer, offset: 0) + + #expect(parsedRA.managedFlag == true) + #expect(parsedRA.otherFlag == false) + } + + @Test + func testRouterAdvertisementWithBothFlags() throws { + // RFC 4861: Both M and O flags set + let ra = try RouterAdvertisement( + currentHopLimit: 255, + managedFlag: true, + otherFlag: true, + routerLifetime: 0xFFFF, + reachableTime: 0xFFFF_FFFF, + retransTimer: 0xFFFF_FFFF + ) + + var buffer = [UInt8](repeating: 0, count: RouterAdvertisement.size) + _ = try ra.appendBuffer(&buffer, offset: 0) + + // M=1, O=1 -> 0xC0 + #expect(buffer[1] == 0xC0) + + var parsedRA = try RouterAdvertisement() + _ = try parsedRA.bindBuffer(&buffer, offset: 0) + + #expect(parsedRA.currentHopLimit == 255) + #expect(parsedRA.managedFlag == true) + #expect(parsedRA.otherFlag == true) + #expect(parsedRA.routerLifetime == 0xFFFF) + #expect(parsedRA.reachableTime == 0xFFFF_FFFF) + #expect(parsedRA.retransTimer == 0xFFFF_FFFF) + } + + @Test + func testRouterAdvertisementZeroLifetime() throws { + // RFC 4861 Section 6.2.5: Router Lifetime 0 means not a default router + let ra = try RouterAdvertisement( + currentHopLimit: 64, + managedFlag: false, + otherFlag: false, + routerLifetime: 0, + reachableTime: 0, + retransTimer: 0 + ) + + var buffer = [UInt8](repeating: 0, count: RouterAdvertisement.size) + _ = try ra.appendBuffer(&buffer, offset: 0) + + var parsedRA = try RouterAdvertisement() + _ = try parsedRA.bindBuffer(&buffer, offset: 0) + + #expect(parsedRA.routerLifetime == 0) + } +} diff --git a/vminitd/Package.resolved b/vminitd/Package.resolved index efd03457..20973fa1 100644 --- a/vminitd/Package.resolved +++ b/vminitd/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", - "version" : "1.26.1" + "revision" : "4b99975677236d13f0754339864e5360142ff5a1", + "version" : "1.30.3" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "a56a157218877ef3e9625f7e1f7b2cb7e46ead1b", - "version" : "1.26.1" + "revision" : "8f57f68b9d247fe3759fa9f18e1fe919911e6031", + "version" : "1.27.1" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "011f0c765fb46d9cac61bca19be0527e99c98c8b", - "version" : "1.5.1" + "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", + "version" : "1.7.0" } }, { @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "a54383ada6cecde007d374f58f864e29370ba5c3", - "version" : "1.3.2" + "revision" : "810496cf121e525d660cd0ea89a758740476b85f", + "version" : "1.5.1" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms.git", "state" : { - "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", - "version" : "1.0.4" + "revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804", + "version" : "1.1.1" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-certificates.git", "state" : { - "revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585", - "version" : "1.10.0" + "revision" : "7d5f6124c91a2d06fb63a811695a3400d15a100e", + "version" : "1.17.1" } }, { @@ -78,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", - "version" : "1.2.0" + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" } }, { @@ -87,8 +87,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto.git", "state" : { - "revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", - "version" : "3.12.3" + "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", + "version" : "3.15.1" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97", + "version" : "1.3.1" } }, { @@ -96,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-structured-headers.git", "state" : { - "revision" : "db6eea3692638a65e2124990155cd220c2915903", - "version" : "1.3.0" + "revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b", + "version" : "1.6.0" } }, { @@ -105,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types.git", "state" : { - "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", - "version" : "1.4.0" + "revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca", + "version" : "1.5.1" } }, { @@ -114,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", - "version" : "1.6.3" + "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181", + "version" : "1.9.1" } }, { @@ -123,8 +132,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "34d486b01cd891297ac615e40d5999536a1e138d", - "version" : "2.83.0" + "revision" : "5e72fc102906ebe75a3487595a653e6f43725552", + "version" : "2.94.0" } }, { @@ -132,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "145db1962f4f33a4ea07a32e751d5217602eea29", - "version" : "1.28.0" + "revision" : "3df009d563dc9f21a5c85b33d8c2e34d2e4f8c3b", + "version" : "1.32.1" } }, { @@ -141,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "4281466512f63d1bd530e33f4aa6993ee7864be0", - "version" : "1.36.0" + "revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80", + "version" : "1.39.0" } }, { @@ -159,8 +168,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56", - "version" : "1.24.0" + "revision" : "60c3e187154421171721c1a38e800b390680fb5d", + "version" : "1.26.0" } }, { @@ -168,8 +177,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-numerics.git", "state" : { - "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", - "version" : "1.0.3" + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" } }, { @@ -177,8 +186,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "102a647b573f60f73afdce5613a51d71349fe507", - "version" : "1.30.0" + "revision" : "c5ab62237f21cad094812719a1bbe29443407c5f", + "version" : "1.34.1" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" } }, { @@ -186,8 +204,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/swift-service-lifecycle.git", "state" : { - "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", - "version" : "2.8.0" + "revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348", + "version" : "2.9.1" } }, { @@ -195,8 +213,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db", - "version" : "1.6.3" + "revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df", + "version" : "1.6.4" } }, { @@ -210,4 +228,4 @@ } ], "version" : 3 -} +} \ No newline at end of file diff --git a/vminitd/Package.swift b/vminitd/Package.swift index 75dae5d1..fc12e444 100644 --- a/vminitd/Package.swift +++ b/vminitd/Package.swift @@ -50,8 +50,10 @@ let package = Package( dependencies: [ .product(name: "Logging", package: "swift-log"), .product(name: "Containerization", package: "containerization"), - .product(name: "ContainerizationNetlink", package: "containerization"), + .product(name: "ContainerizationExtras", package: "containerization"), + .product(name: "ContainerizationICMP", package: "containerization"), .product(name: "ContainerizationIO", package: "containerization"), + .product(name: "ContainerizationNetlink", package: "containerization"), .product(name: "ContainerizationOS", package: "containerization"), .product(name: "SystemPackage", package: "swift-system"), "LCShim", diff --git a/vminitd/Sources/vminitd/DNSMonitor.swift b/vminitd/Sources/vminitd/DNSMonitor.swift new file mode 100644 index 00000000..c6798ba8 --- /dev/null +++ b/vminitd/Sources/vminitd/DNSMonitor.swift @@ -0,0 +1,176 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Containerization +import ContainerizationExtras +import ContainerizationICMP +import Foundation +import Logging +import SystemPackage + +private struct IPv6Nameserver { + let address: IPv6Address + let expiry: Date +} + +actor DNSMonitor { + private static let maxNameservers = 3 + + private var configs: [FilePath: DNS] = [:] + + private var ipv6Nameservers: [IPv6Nameserver] = [] + + private let log: Logger + + private let icmpV6Session: ICMPv6Session + + init(log: Logger) throws { + self.log = log + self.icmpV6Session = try ICMPv6Session() + } + + func update(resolvConfPath: FilePath, config: DNS) throws { + let parentPathname = resolvConfPath.removingLastComponent().string + try FileManager.default.createDirectory(atPath: parentPathname, withIntermediateDirectories: true) + + let mergedNameservers: [String] + if config.nameservers.count < Self.maxNameservers { + mergedNameservers = config.nameservers + ipv6Nameservers.map { $0.address.description } + } else { + mergedNameservers = config.nameservers.prefix(2) + ipv6Nameservers.map { $0.address.description } + } + + let mergedConfig = DNS( + nameservers: mergedNameservers, + domain: config.domain, + searchDomains: config.searchDomains, + options: config.options + ) + + let text = mergedConfig.resolvConf + log.debug("updating resolver configuration", metadata: ["path": "\(resolvConfPath)"]) + try text.write(toFile: resolvConfPath.string, atomically: true, encoding: .utf8) + configs[resolvConfPath] = config + } + + func run() async throws { + self.log.info("starting DNS monitor") + while true { + let now = Date.now + let timeInterval = + ipv6Nameservers + .map { $0.expiry.timeIntervalSince(now) } + .compactMap { $0 >= 0 ? $0 : nil } + .min() + do { + if timeInterval == nil { + self.log.info("sending router solicitation") + try sendRouterSolicitation() + } + } catch { + log.warning("router solicitation send failed", metadata: ["error": "\(error)"]) + try await Task.sleep(for: .seconds(1)) + continue + } + + do { + let timeout = Duration.seconds(timeInterval ?? 1.0) + log.info("awaiting router advertisement", metadata: ["timeoutSecs": "\(timeout)"]) + var lifetimesByAddress = try await getIpv6Nameservers(timeout: timeout) + var newNameservers: [IPv6Nameserver] = [] + let now = Date.now + for nameserver in ipv6Nameservers { + guard let lifetime = lifetimesByAddress[nameserver.address] else { + // No update, carry it over. + newNameservers.append(nameserver) + continue + } + + // Remove since we're deleting or merging. + lifetimesByAddress.removeValue(forKey: nameserver.address) + if lifetime == 0 { + // Zero lifetime, so delete. + continue + } + + // Merge new expiry into existing entry. + newNameservers.append(.init(address: nameserver.address, expiry: now.addingTimeInterval(Double(lifetime)))) + } + + // Add remaining entries. + for (address, lifetime) in lifetimesByAddress { + newNameservers.append(.init(address: address, expiry: now.addingTimeInterval(Double(lifetime)))) + } + + self.ipv6Nameservers = newNameservers + } catch { + log.warning("router advertisement receive failed", metadata: ["error": "\(error)"]) + } + + do { + for (resolvConfPath, dns) in configs { + log.info("awaiting DNS", metadata: ["path": "\(resolvConfPath)"]) + try update(resolvConfPath: resolvConfPath, config: dns) + } + } catch { + log.warning("DNS update failed", metadata: ["error": "\(error)"]) + } + } + } + + private func sendRouterSolicitation() throws { + let interface = "eth0" + guard let linkLayerAddress = MACAddress.fromZone(interface) else { + throw AddressError.invalidZoneIdentifier + } + _ = try icmpV6Session.routerSolicitation(linkLayerAddress: linkLayerAddress, interface: interface) + } + + private func getIpv6Nameservers(timeout: Duration) async throws -> [IPv6Address: UInt32] { + var result = try await withCheckedThrowingContinuation { continuation in + DispatchQueue.global().async { + do { + let result = try self.icmpV6Session.recv( + type: .routerAdvertisement, + timeout: timeout + ) + continuation.resume(returning: result) + } catch { + continuation.resume(throwing: error) + } + } + } + + // Parse router advertisement. + var routerAdvertisement = try RouterAdvertisement() + let offset = try routerAdvertisement.bindBuffer(&result.bytes, offset: result.offset) + + // Parse options, adding RDNSS server addresses and lifetimes. + let remainingBytes = result.length - offset + let options = try result.bytes.parseNDOptions(offset: offset, length: remainingBytes) + var lifetimesByAddress: [IPv6Address: UInt32] = [:] + for option in options { + switch option { + case .recursiveDNSServer(let lifetime, let addresses): + addresses.forEach { lifetimesByAddress[$0] = lifetime } + default: + continue + } + } + + return lifetimesByAddress + } +} diff --git a/vminitd/Sources/vminitd/InitCommand.swift b/vminitd/Sources/vminitd/InitCommand.swift index 6dc9c4fe..7cef04bd 100644 --- a/vminitd/Sources/vminitd/InitCommand.swift +++ b/vminitd/Sources/vminitd/InitCommand.swift @@ -133,7 +133,8 @@ struct InitCommand { t.start() let eg = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - let server = Initd(log: log, group: eg) + let dnsMonitor = try DNSMonitor(log: log) + let server = Initd(log: log, group: eg, dnsMonitor: dnsMonitor) do { log.info("serving vminitd API") diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index df057689..d2687afb 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -27,6 +27,7 @@ import Logging import NIOCore import NIOPosix import SwiftProtobuf +import SystemPackage private let _setenv = Foundation.setenv @@ -1020,19 +1021,16 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid ]) do { - let etc = URL(fileURLWithPath: request.location).appendingPathComponent("etc") - try FileManager.default.createDirectory(atPath: etc.path, withIntermediateDirectories: true) - let resolvConf = etc.appendingPathComponent("resolv.conf") + let resolvConfPath = FilePath(request.location) + .appending("etc") + .appending("resolv.conf") let config = DNS( nameservers: request.nameservers, domain: domain, searchDomains: request.searchDomains, options: request.options ) - let text = config.resolvConf - log.debug("writing to path \(resolvConf.path) \(text)") - try text.write(toFile: resolvConf.path, atomically: true, encoding: .utf8) - log.debug("wrote resolver configuration", metadata: ["path": "\(resolvConf.path)"]) + try await dnsMonitor.update(resolvConfPath: resolvConfPath, config: config) } catch { log.error( "configureDns", diff --git a/vminitd/Sources/vminitd/Server.swift b/vminitd/Sources/vminitd/Server.swift index 93f6abeb..2408cf5a 100644 --- a/vminitd/Sources/vminitd/Server.swift +++ b/vminitd/Sources/vminitd/Server.swift @@ -19,7 +19,6 @@ import Foundation import GRPC import Logging import NIOCore -import NIOPosix final class Initd: Sendable { actor State { @@ -77,12 +76,14 @@ final class Initd: Sendable { } let log: Logger - let state: State let group: EventLoopGroup + let dnsMonitor: DNSMonitor + let state: State - init(log: Logger, group: EventLoopGroup) { + init(log: Logger, group: EventLoopGroup, dnsMonitor: DNSMonitor) { self.log = log self.group = group + self.dnsMonitor = dnsMonitor self.state = State() } @@ -110,9 +111,13 @@ final class Initd: Sendable { "port": "\(port)" ]) + group.addTask { + try await self.dnsMonitor.run() + } group.addTask { try await server.onClose.get() } + try await group.next() log.info("closing gRPC server") group.cancelAll()