Skip to content

Commit 9839193

Browse files
Guard against if ipv4Gateway is nil, removed fore unwrap (#524)
- Guarded against ipv4Gateway if nil and throw `ContainerizationError(.invalidState, ...)` instead of force unwraping - Added a unit test `ContainerManagerTests.testCreateThrowsWhenGatewayMissing`
1 parent 3f9dcf6 commit 9839193

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

Sources/Containerization/ContainerManager.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,13 @@ public struct ContainerManager: Sendable {
440440
}
441441
if let interface = try self.network?.create(id) {
442442
config.interfaces = [interface]
443-
// FIXME: throw instead of crash here if we can't unwrap?
444-
config.dns = .init(nameservers: [interface.ipv4Gateway!.description])
443+
guard let gateway = interface.ipv4Gateway else {
444+
throw ContainerizationError(
445+
.invalidState,
446+
message: "missing ipv4 gateway for container \(id)"
447+
)
448+
}
449+
config.dns = .init(nameservers: [gateway.description])
445450
}
446451
config.bootLog = BootLog.file(path: self.containerRoot.appendingPathComponent(id).appendingPathComponent("bootlog.log"))
447452
try configuration(&config)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2026 Apple Inc. and the Containerization project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import Containerization
18+
import ContainerizationArchive
19+
import ContainerizationError
20+
import ContainerizationExtras
21+
import Foundation
22+
import Testing
23+
24+
@testable import Containerization
25+
26+
private struct NilGatewayInterface: Interface {
27+
let ipv4Address: CIDRv4
28+
let ipv4Gateway: IPv4Address? = nil
29+
let macAddress: MACAddress? = nil
30+
31+
init() {
32+
self.ipv4Address = try! CIDRv4("192.168.64.2/24")
33+
}
34+
}
35+
36+
private struct NilGatewayNetwork: ContainerManager.Network {
37+
mutating func create(_ id: String) throws -> Interface? {
38+
NilGatewayInterface()
39+
}
40+
41+
mutating func release(_ id: String) throws {}
42+
}
43+
44+
@Suite
45+
struct ContainerManagerTests {
46+
@Test func testCreateThrowsWhenGatewayMissing() async throws {
47+
let fm = FileManager.default
48+
let root = fm.uniqueTemporaryDirectory(create: true)
49+
defer { try? fm.removeItem(at: root) }
50+
51+
let kernelPath = root.appendingPathComponent("vmlinux")
52+
fm.createFile(atPath: kernelPath.path, contents: Data(), attributes: nil)
53+
let initfsPath = root.appendingPathComponent("initfs.ext4")
54+
fm.createFile(atPath: initfsPath.path, contents: Data(), attributes: nil)
55+
56+
let kernel = Kernel(path: kernelPath, platform: .linuxArm)
57+
let initfs = Mount.block(format: "ext4", source: initfsPath.path, destination: "/")
58+
59+
var manager = try ContainerManager(
60+
kernel: kernel,
61+
initfs: initfs,
62+
root: root,
63+
network: NilGatewayNetwork()
64+
)
65+
66+
let tempDir = fm.uniqueTemporaryDirectory()
67+
defer { try? fm.removeItem(at: tempDir) }
68+
69+
let tarPath = Foundation.Bundle.module.url(forResource: "scratch", withExtension: "tar")!
70+
let reader = try ArchiveReader(format: .pax, filter: .none, file: tarPath)
71+
let rejectedPaths = try reader.extractContents(to: tempDir)
72+
#expect(rejectedPaths.isEmpty)
73+
74+
let images = try await manager.imageStore.load(from: tempDir)
75+
let image = images.first!
76+
77+
let rootfsPath = root.appendingPathComponent("rootfs.ext4")
78+
fm.createFile(atPath: rootfsPath.path, contents: Data(), attributes: nil)
79+
let rootfs = Mount.block(format: "ext4", source: rootfsPath.path, destination: "/")
80+
81+
do {
82+
_ = try await manager.create("test-nil-gateway", image: image, rootfs: rootfs) { _ in }
83+
#expect(Bool(false), "expected invalidState error for missing ipv4 gateway")
84+
} catch let error as ContainerizationError {
85+
#expect(error.code == .invalidState)
86+
#expect(error.message.contains("missing ipv4 gateway"))
87+
} catch {
88+
#expect(Bool(false), "unexpected error: \(error)")
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)