Commit Graph
187 Commits
Author SHA1 Message Date
Yibo Zhuang 03280f3952 refactor memory alignment to VZVirtualMachineInstance (#725)
Move the MiB rounding from LinuxContainer into
`VZVirtualMachineInstance`, so all callers are covered in one place and
the alignment constraint stays with the VMM layer that owns it.

This will also ensure LinuxPod can get memory alignment for free.
2026-05-06 07:36:10 -07:00
Danny Canter 57d157c82a Signal: Expose helpful helper methods (#721)
- Method to grab the signal name
- Convert macOS signal to linux equivalent
2026-05-01 14:51:40 -07:00
Michael CrosbyandDanny Canter b62edd2e2d make vminitd package for extensions (#708)
Signed-off-by: michael_crosby <michael_crosby@apple.com>
Co-authored-by: Danny Canter <danny_canter@apple.com>
2026-04-29 14:43:52 -04:00
Danny Canter 0e469481f5 LinuxContainer: Allow cpu/mem overhead to be configurable (#710)
If someone wanted to use a custom guest agent/guest image in general the
overhead we chose is kind of a nuisance.. This allows the values to be
configurable, and raises the default to 128, and vminitds cgroup
monitoring to 80.
2026-04-29 10:33:44 -04:00
Yibo Zhuang 9a6f0d4c52 Add support for network block device (NBD) (#690)
This change adds support for attaching network block device (NBD) to
both LinuxContainer and LinuxPod.

For LinuxContainer, whether to use the underlying
`VZNetworkBlockDeviceStorageDeviceAttachment` is determined by the URL
of the container Mount source.

For LinuxPod, adds additional API to support pod-level volumes that can
be mounted into multiple containers. The PodVolume type provides enum to
support multiple types of volume source. LinuxContainer can reference
the pod level volume using the `Mount.sharedMount()` constructor with
the name referencing the name of the pod volume. This will allow the NBD
to be attached to the pod at the VM level and then bind-mounted into the
container.

For integration tests, added a lightweight NBD server implementation in
swift that speaks the NBD protocol to ensure there is sufficient
coverage for the changes introduced.

Apple Virtualization NBD support documentation:

https://developer.apple.com/documentation/virtualization/vznetworkblockdevicestoragedeviceattachment
2026-04-28 10:56:17 -07:00
Dmitry Kovba 3ef9976d38 Fix compilation warnings (#696)
Fix compilation warnings:

```
.../Sources/Containerization/LinuxContainer.swift:877:35: warning: 'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope [#ImplicitStrongCapture]
 851 |     /// via the `start` method.
 852 |     public func exec(_ id: String, configuration: @Sendable @escaping (inout LinuxProcessConfiguration) throws -> Void) async throws -> LinuxProcess {
 853 |         try await self.state.withLock { state in
     |                                       |- note: 'self' implicitly strongly captured here
     |                                       `- note: add 'self' as a capture list item to silence
 854 |             var startedState = try state.startedState("exec")
 855 | 
     :
 875 |                 vm: startedState.vm,
 876 |                 logger: self.logger,
 877 |                 onDelete: { [weak self] in
     |                                   |- warning: 'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope [#ImplicitStrongCapture]
     |                                   `- note: explicitly assign the capture list item to silence
 878 |                     await self?.removeProcess(id: id)
 879 |                 }

.../Sources/Containerization/LinuxContainer.swift:914:35: warning: 'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope [#ImplicitStrongCapture]
 890 |     /// via the `start` method.
 891 |     public func exec(_ id: String, configuration: LinuxProcessConfiguration) async throws -> LinuxProcess {
 892 |         try await self.state.withLock {
     |                                       |- note: 'self' implicitly strongly captured here
     |                                       `- note: add 'self' as a capture list item to silence
 893 |             var state = try $0.startedState("exec")
 894 | 
     :
 912 |                 vm: state.vm,
 913 |                 logger: self.logger,
 914 |                 onDelete: { [weak self] in
     |                                   |- warning: 'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope [#ImplicitStrongCapture]
     |                                   `- note: explicitly assign the capture list item to silence
 915 |                     await self?.removeProcess(id: id)
 916 |                 }

[#ImplicitStrongCapture]: <https://docs.swift.org/compiler/documentation/diagnostics/implicit-strong-capture>
```
2026-04-28 11:18:13 -04:00
Danny Canter 56f1c83a14 Rework signals usage/parsing (#704)
Replace the old platform-gated Signals helper in ContainerizationOS with
a Signal struct in Containerization. Signal provides named constants for
common signals (.kill, .term, etc.), platform-specific namespaces
(Signal.Linux for rtmin/stkflt/pwr, Signal.Darwin for emt/info), string
parsing, and ExpressibleByIntegerLiteral support.

LinuxContainer, LinuxProcess, and LinuxPod kill APIs now take Signal
instead of raw Int32.
2026-04-28 11:15:13 -04:00
jwhur b770cd2a02 Add stat RPC (#614)
Add `Stat` RPC to vminitd.

`Stat` RPC is used to stat file system objects in the VM. It accepts the
path to the object (resolved against VM root) and returns the stat
result.
2026-04-27 14:44:33 -07:00
Michael Crosby 59f599eed5 allow filter of descriptors on push (#703)
Signed-off-by: michael_crosby <michael_crosby@apple.com>
2026-04-27 16:57:20 -04:00
Danny Canter 46c8fd3269 LinuxContainer/LinuxPod: Expose withVirtualMachineInstance (#701) 2026-04-27 12:17:12 -07:00
Dmitry Kovba 41ffd07f3e Add a default route to the first interface only (#697)
Adds a default route to the first interface only.
2026-04-23 11:05:25 -04:00
Dmitry Kovba bbea928c9e Add a public constructor to HTTP2ConnectBufferingHandler.swift (#695)
Adds a public constructor to `HTTP2ConnectBufferingHandler.swift`. This
is a follow-up to https://github.com/apple/containerization/pull/694.
2026-04-21 15:57:25 -07:00
Aditya Ramani 7874529578 Make HTTP2ConnectBufferingHandler public temporarily (#694)
Make the `HTTP2ConnectBufferingHandler` class public temporarily so that
consumers of the containerization package may reuse it for other grpc
clients.
2026-04-21 15:29:37 -07:00
Danny Canter 47518e0738 LinuxContainer: Give 1 vcpu of overhead for guest (#684) 2026-04-14 00:19:40 -07:00
Danny Canter 833b07f4f5 vminitd (client): Turn off idle timeout (#680)
Closes: #678

There's no reason to have an idle timeout per connection as users may
use this library in many different ways. The grpc lib defaults to a 30
min idle timeout which could be trivially reached if instead of waiting
on the init process, you were waiting on a bunch of execs. NIO under the
hood asserts on ebadfs (which is good, but bad for this scenario..), so
lets turn off the timeout altogether.
2026-04-13 00:15:37 -07:00
J LoganandDanny Canter 9d870451e8 Upgrade vminitd API to use grpc-swift-2. (#578) (#669)
- Closes #51. Again.
- No real notable API changes. The largest change code-wise is in
Vminitd. The connection loop is set up immediately in the new API by
calling runConnections() on the client, and this blocks until either the
task it's running on is cancelled or beginGracefulShutdown is called.
Because of this, either we'd have to modify our API to have
VirtualMachineAgent implementations call some run() method, or we just
put runConnections in an internal task on Vminitd. This change uses the
latter approach as it's simpler.
- Updated dependencies to pick up latest protobuf and grpc-swift-2
fixes.
- ClientBootstrap.withConnectedSocket(fd).wait() registers the vsock fd
with epoll/kqueue immediately. vminitd sends its HTTP/2 SETTINGS frame
right away upon accepting the connection, so those bytes can arrive on
the fd — and be read by NIO — before the gRPC pipeline is installed.
With a bare pipeline, NIO has nowhere to send the bytes and discards
them.
- Buffer until ClientConnectionHandler is in the pipeline so it can
process the SETTINGS frame and fire .ready, then schedule delivery of
the buffered data with assumeIsolatedUnsafeUnchecked().execute.
- To help produce more accurate merged logs, defines a
`StderrLogHandler` in the vminitd Application.swift that writes
timestamps to the boot log with millisecond precision and format
identical to that of the log files created using the `--log-root` arg on
`container`.
- Updates hawkeye to latest version (v6.5.1).
- In vminitd, use the Server event loop group for the gRPC server.

Co-authored-by: Danny Canter <danny_canter@apple.com>
2026-04-12 22:11:03 -07:00
Danny Canter ceee3a67ba vminitd: Lower nio event loop group threads to 1 (#677) 2026-04-10 10:47:22 -07:00
Danny Canter 8451a8427d Single file mounts: Add/fix up some comments (#675) 2026-04-09 10:21:07 -07:00
Danny Canter ab89983c56 Mounts: Change single file mounts to a different approach (#665)
Related to https://github.com/apple/container/issues/1251

Change to sharing in the parent directory, and then bind mounting in the
file into the container. This has the unfortunate reality of being less
secure, but the current approach is burdended by a couple things,
namely:

1. You can't share in files that are on a different volume
2. There is a Virtualization bug that causes spurious errors when trying
to open the file.

I've added a doc to go over the approach we've taken, and some
workarounds if the approach is not satisfactory.
2026-04-08 22:01:13 -07:00
Raj 636715513c Add multi-reference push to ImageStore (#661) 2026-04-07 16:41:58 -07:00
Danny Canter a1038a9841 make default goal compile on Linux (#648)
To be able to test vminitd/vmexec/linux specific packages on ci it'd be
a heck of a lot easier if `make` just worked. This should be the last
bit needed. The default goal currently compiles just fine after the
linux specific `make deps` is ran. Next in line would be adding decent
unit tests/actually getting ci setup for the linux bits.
2026-04-06 11:13:16 -07:00
Danny Canter 367c136705 VirtualMachineInstance: Make default impls public (#651) 2026-04-06 10:34:32 -07:00
Danny Canter 1d60473274 LinuxPod: Remove macOS ifdef (#643)
It compiles fine on non-macOS
2026-04-03 12:31:42 -07:00
Danny Canter 3c4215702b LinuxContainer/LinuxPod: Sort mounts by depth (#601) 2026-04-03 10:28:47 -07:00
Anthony 35f97f225d Add progress handler for rootfs unpacking (#515)
I've added an optional progress handler for rootfs unpacking so that
consumers can show progress. The Ubuntu image takes about 8 seconds to
unpack on my machine, and I'm developing an app where it would be useful
to show progress in the UI.

Total size is determined in an optional first pass that scans archive
headers. Bytes written are then reported during unpacking. The optional
first pass adds 15 (Alpine) to 115 (Ubuntu) ms to unpacking duration on
my machine, depending on image size.
2026-04-02 17:33:55 -07:00
Dmitry Kovba 11bbbbb852 Support a default route without a gateway (#628)
Adds the support for a default route without a gateway.

```
/ # ip route show default
default dev eth0 scope link 
```
2026-04-02 14:27:56 -07:00
Danny Canter 8615e3b021 Remove more incorrect os gates (#632)
A lot of logic we have works perfectly fine on other OS'.
2026-04-02 10:12:55 -07:00
Danny Canter 4c28821fef LinuxContainer: Move memory clamping to VZ code (#630)
The memory limit (clamp to system ram) is a VZ limitation so it's weird
that we had that logic directly in LinuxContainer. Someone could make a
VZVirtualMachineInstance that doesn't have this limitation but we
inherit this logic because it's done before passing off the memory
amount to the VMM.
2026-04-01 20:17:14 -07:00
Danny Canter baccf3f44b Hash: Make package visibility/remove ifdef (#631)
These build fine on Linux, and I don't honestly know why we had
hashMountSource as public API..
2026-04-01 19:45:53 -07:00
Danny Canter 220042089b Remove LinuxContainer.setupIO (#627)
Dead code
2026-04-01 13:57:43 -07:00
Danny Canter c5c1500eac Remove some unneeded Foundation imports (#613) 2026-03-30 17:00:58 -07:00
Maxime Grenu 1951816427 fix(dns): validate nameserver addresses are valid IP addresses (#534)
## What

Add a `DNS.validate()` method that verifies all nameserver entries are
valid IPv4 or IPv6 addresses. The method is called from
`Vminitd.configureDNS()` before applying the configuration.

## Why

Closes #467. Currently, any arbitrary string can be passed as a
nameserver in `DNSConfiguration`, which silently results in an invalid
`/etc/resolv.conf` inside the container. Hostname strings like
`dns.example.com` would be written to resolv.conf but would not work as
nameservers.

## How

- Added `DNS.validate() throws` method in `DNSConfiguration.swift` that
iterates over all nameservers and attempts to parse each as either an
`IPv4Address` or `IPv6Address` (using the existing parsers in
`ContainerizationExtras`)
- Added `import ContainerizationExtras` to `DNSConfiguration.swift` (the
`Containerization` target already depends on `ContainerizationExtras`)
- Called `config.validate()` at the start of `Vminitd.configureDNS()` so
validation happens before any GRPC call

## Testing

Added 6 new unit tests in `DNSTests.swift`:
- ✅ Valid IPv4 nameservers accepted
- ✅ Valid IPv6 nameservers accepted  
- ✅ Mixed IPv4/IPv6 nameservers accepted
- ✅ Empty nameserver list accepted
- ❌ Hostname rejected
- ❌ Invalid address rejected

## Checklist

- [x] Tests added
- [x] No breaking changes to existing API (validate() is a new method,
existing init is unchanged)
- [x] Follows existing patterns (uses ContainerizationExtras IP address
types, ContainerizationError for errors)

Signed-off-by: Maxime Grenu <maxime.grenu@gmail.com>
2026-03-25 02:40:07 -07:00
Danny Canter d8fcd1e9ca Network: Move to standalone type (#584)
The network protocol and VMNetNetwork implementation currently are
housed on ContainerManager even though they are generally useful types
even outside of this easy to use helper type. This change moves them to
not be nested types anymore, as well as exposes a new param on
VMNetNetworks constructor so we can pass the type of network.
2026-03-19 11:33:46 -07:00
Michael Crosby 84b71f10ae Revert "Upgrade vminitd API to use grpc-swift-2. (#578)" (#588)
This reverts commit 7ffe6d2ed5.
2026-03-17 13:46:28 -07:00
Dmitry Kovba 8df7ce78d7 Enforce progress event value types (#579)
Enforces progress event value types.
2026-03-17 09:23:45 -07:00
Danny Canter a35b168888 LinuxContainer/LinuxPod: Add support for NoNewPrivileges (#581)
Basically just a direct mapping to the prctl/OCI spec field.
2026-03-14 11:23:22 -07:00
J LoganandDanny Canter 7ffe6d2ed5 Upgrade vminitd API to use grpc-swift-2. (#578)
- No real notable API changes. The largest change code-wise is in
Vminitd. The connection loop is set up immediately in the new API by
calling runConnections() on the client, and this blocks until either the
task it's running on is cancelled or beginGracefulShutdown is called.
Because of this, either we'd have to modify our API to have
VirtualMachineAgent implementations call some run() method, or we just
put runConnections in an internal task on Vminitd. This change uses the
latter approach as it's simpler.
- Updated dependencies to pick up latest protobuf and grpc-swift-2
fixes.
- ClientBootstrap.withConnectedSocket(fd).wait() registers the vsock fd
with epoll/kqueue immediately. vminitd sends its HTTP/2 SETTINGS frame
right away upon accepting the connection, so those bytes can arrive on
the fd — and be read by NIO — before the gRPC pipeline is installed.
With a bare pipeline, NIO has nowhere to send the bytes and discards
them.
- Buffer until ClientConnectionHandler is in the pipeline so it can
process the SETTINGS frame and fire .ready, then schedule delivery of
the buffered data with assumeIsolatedUnsafeUnchecked().execute.
- To help produce more accurate merged logs, defines a
`StderrLogHandler` in the vminitd Application.swift that writes
timestamps to the boot log with millisecond precision and format
identical to that of the log files created using the `--log-root` arg on
`container`.
- Updates hawkeye to latest version (v6.5.1).

---------

Co-authored-by: Danny Canter <danny_canter@apple.com>
2026-03-13 12:26:09 -07:00
Dmitry Kovba 622d102b8e Fix a potential filesystem corruption on virtio block devices (#576)
Enable the caching mode for virtio block devices to fix a potential
filesystem corruption. Also see
https://github.com/apple/container/pull/1041.
2026-03-12 20:17:30 -07:00
Danny Canter 1be13d1d76 LinuxContainer: Support copying directories (#571)
Now that the static linux SDK we use has libarchive linked against it,
we can finally live our dream (/s) of supporting copying directories
in/out a little easier. This was.. kind of annoying. I think the very
simple route of writing archive to guest/host -> streaming over grpc is
simplest, but on the guest end we have a couple problems:

1. The VMs rootfs is read only today always, which is a good thing to me
and I don't want to change if we don't have to.
2. Writing the archive to the containers rootfs temporarily could work,
but it's a bit weird, and the user can make the containers rootfs
readonly which would screw that plan.
3. We could write it to /run or /tmp, but they're tmpfs and dealing with
the headache of the user possibly tarring an enormous dir is one I don't
want to care about.

So, that leaves us with the (truthfully better to me) approach of trying
to write the tar data directly to the host and skipping grpc which kinda
forces us to have a temp spot. Because of that, I made it such that we
pass a port number from host<->guest, and transfer the actual data
(either the single file or tarred dir contents) over the vsock port
instead. The stream is kinda clunky, but it just serves as a means to
exchange metadata and a "we're done" signifier.
2026-03-06 15:30:00 -08:00
Danny Canter 7064cbab40 vminitd: Bump Swift version to 6.3-snapshot (#570)
Closes #221

This contains (in the static linux sdk but the swift version and the sdk
must match) libarchive support and mimalloc! This also (unrelated to the
sdk bump it seems) raises the amount of retries we do when dialing
vminitd. It seems CI on rare occasions recently has timed out on some
tests. We could also explore just lowering the concurrency on the
integration suite.
2026-03-04 19:11:46 -08:00
Danny Canter 6f9ae02933 LinuxContainer/LinuxPod: Fix UDS proxy if target is symlink (#568)
Closes #256

If the target of the uds proxy goes through a symlink (/var/run) we had
the same bug we had with mounts previously where we'd follow this and if
the resolved path existed in the root of the VM we'd end up mounting
there. Fix this by plopping the sockets in a holding spot and then bind
mounting them in (which has the correct resolution logic).
2026-03-03 10:41:28 -08:00
Yibo Zhuang bacd99619f linux pod: exec process should inherit container's process config (#562)
This change fixes an issue in linux pod where the exec process
does not inherit the container process config which means any
environment variables, working directory, user, rlimits,
capabilities would be different for the exec process.

This change ensures that exec process would inherit
environment variables, working directory, user, rlimits,
and capabilities from the container process config but
resets arguments, terminal, and stdio as those should not
be inherited.

This change also adds an integration test to exercise this.
2026-02-26 21:22:52 -08:00
Danny Canter 205ccc7774 LinuxPod: Add hostname field on pod config (#561)
Just like we have for hosts and dns configurations, we can add a
hostname field to make it easier to setup if every container is going to
be using the same hostname.
2026-02-26 13:41:40 -08:00
Anthony b6b95ac897 Add per-container networking flag to ContainerManager.create() (#549)
This PR adds a `networking: Bool = true` parameter to
`ContainerManager.create()` so callers can opt out of network interface
creation on a per-container basis.

## Motivation

Currently, `ContainerManager.create()` unconditionally allocates a vmnet
network interface for every container (when the manager has a network
configured). Some use cases don't need network access and benefit from
having it disabled to reduce attack surface.

There's no way to achieve this today without either:
- Initializing the `ContainerManager` without a network (which disables
networking for *all* containers)
- Clearing `config.interfaces` in the configuration closure (which
wastes an IP allocation from the vmnet pool since `network.create(id)`
has already been called)

## Changes

- Add `networking: Bool = true` to all three `create()` overloads on
`ContainerManager`
- When `false`, `self.network?.create(id)` is skipped entirely. No
interface is allocated, and no DNS is configured.
- `releaseNetwork`/`delete` remain safe to call regardless, since
`Allocator.release` silently ignores unknown IDs.
- Add unit test `testNetworkingFalseSkipsInterfaceCreation` using the
existing `NilGatewayNetwork` fixture
- Add integration tests `testNetworkingDisabled` and
`testNetworkingEnabled` that create containers through a network-enabled
`ContainerManager` and verify the presence/absence of `eth0` via
`/sys/class/net/`
2026-02-22 17:08:49 -08:00
Danny Canter 32cf8c4d32 vminitd: Implement minimal init process to reap zombies (#537)
There are some workloads that benefit from having a "true" init process
that reaps zombies. This change implements a minimal init as part of
vminitd and exposes an API to be able to use this init process in our
containers. signals will be forwarded, any child procs will be reaped
and the exit code of the actual workload will be propagated. The
LinuxContainer/LinuxPod API is a very simple bool to ask to use this as
our init process, and the init simply spawns whatever command you want
as a child underneath this.

```
➜  ./bin/cctl run --kernel bin/kernel.arm64 --init
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /.cz-init -- /bin/sh
    2 root      0:00 /bin/sh
    3 root      0:00 ps aux
```
2026-02-20 16:22:36 -08:00
Danny Canter 061bc0a071 LinuxContainer: Add support for optional writable layer (#533)
This adds support to LinuxContainer to be able to provide an optional
writable layer. This is useful as today the rootfs size is determined by
the size of the image block itself which is set in stone at unpack time
of the image. This leaves a lot to be desired because a user might want
a larger or smaller rootfs size, but it's not configurable on a per
container basis because of this. To support this, we can pass an
additional writable block device that we can overlayfs with the image
contents in the guest. All writes will go to this writable layer, and
the rootfs size in the container is now whatever size this writable
layer is.

The block devices (via our ext4 package, but you could use whatever) are
cheap enough to generate on the fly that you can choose whatever size
you want easily.
2026-02-19 15:27:43 -08:00
Yibo Zhuang 6cb52e2465 vminitd: add anonymous memory and inactive file stats (#538)
These two stats are import to calculate the following
- wss bytes (memory usage - inactive files)
- rss bytes (anonymous memory)
- available bytes (limit - wss bytes)

Just exposing these two in `SandboxContext.proto` as we already collect
them in `readMemoryStats()` in `Cgroup2Manager`
2026-02-18 20:28:55 -08:00
Dmitry Kovba 36002eed5e Fix the socket relay error handling and log level (#535)
- Downgrades a warning to the debug level in `BidirectionalRelay`
because the shutdown syscall isn't supported for certain socket types
- Rethrows an error if we can't start a socket relay
2026-02-18 15:55:50 -08:00
Anthony 9bce347f50 Add releaseNetwork method to ContainerManager (#527)
I've extracted the network cleanup step from `delete` into a new public
`releaseNetwork` method. This mirrors the existing `release` on
`VmnetNetwork` and enables stop-and-restart workflows where the network
allocation needs to be freed without removing the container's rootfs.

## Changes

- Add `ContainerManager.release(_:)` — frees network resources for a
container after it has stopped
- Refactor `delete(_:)` to call `releaseNetwork(_:)` before removing
files, preserving existing behavior

## Motivation

Currently the only way to free a container's network allocation is
`delete`, which also removes its files. This makes it impossible to stop
a container (freeing the network so the ID can be re-allocated) and
later restart it with its filesystem intact. The new `releaseNetwork `
method fills this gap.
2026-02-12 12:11:29 -08:00
Dmitry Kovba 7c7ed05e10 Add a public method to wait for the relay to complete (#529)
- Adds a public method to wait for the relay to complete when both
directions close
- Improves error handling
- Moves `UnixSocketRelayManager` into a separate file
- Renames `SocketRelay` to `UnixSocketRelay`
- Adds logs at the info level after converting other logs to the debug
level previously
2026-02-11 23:44:22 -08:00