# How to Implement Custom Network Plugins with container-network-vmnet

> Learn to implement custom network plugins with container-network-vmnet by building a Swift package, configuring NetworkConfiguration, and registering an XPC service for the container runtime.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-06-21

---

**Implementing custom network plugins with container-network-vmnet requires creating a Swift package that implements the `Network` protocol, configures a `NetworkConfiguration` object, and registers as an XPC service that the container runtime can discover in the plugins directory.**

The `apple/container` repository provides a built-in network plugin called `container-network-vmnet` that interfaces with Apple's **vmnet** kernel extension to provide NAT and host-only networking for containers. Understanding how to implement custom network plugins with container-network-vmnet allows you to create specialized network configurations such as custom DHCP servers, specific bridge layouts, or pre-seeded IPv6 prefixes while maintaining full integration with the container runtime.

## Understanding container-network-vmnet Architecture

The `container-network-vmnet` plugin operates as an XPC service that implements the `Network` protocol defined in **ContainerNetworkServer**. It provides two distinct operational modes controlled via the `--variant` flag, each suited for different macOS versions and networking requirements.

### The Two Operation Modes

**`allocationOnly`** creates a lightweight NAT bridge available on all supported macOS versions. In this mode, the plugin only allocates an IPv4 subnet (defaulting to `192.168.64.1/24`) and reports it to the container runtime without making any vmnet reservation. This implementation resides in [`Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift`](https://github.com/apple/container/blob/main/Sources/Services/NetworkVmnet/Server/AllocationOnlyVmnetNetwork.swift).

**`reserved`** utilizes the vmnet reservation APIs to create a fully-controlled network supporting both IPv4 and IPv6. This mode requires **macOS 26 or later** (checked at runtime) and maintains ownership of the `vmnet_network_ref`. The plugin can expose the underlying XPC object to the container, enabling more advanced networking scenarios. This logic is implemented in [`Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift`](https://github.com/apple/container/blob/main/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift).

### Core Components and File Structure

The plugin architecture centers on several key files:

- **[`Sources/Plugins/NetworkVmnet/NetworkVmnetHelper.swift`](https://github.com/apple/container/blob/main/Sources/Plugins/NetworkVmnet/NetworkVmnetHelper.swift)**: Defines the `NetworkVmnetHelper` struct, which serves as the CLI entry point and declares the XPC command name.
- **`Sources/Plugins/NetworkVmnet/NetworkVmnetHelper+Start.swift`**: Parses CLI arguments including `--id`, `--mode`, `--subnet`, `--subnet-v6`, and `--variant`, then constructs the appropriate network implementation.
- **[`Sources/ContainerResource/Network/NetworkConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Network/NetworkConfiguration.swift)**: Carries user-supplied parameters including the network name, mode, optional subnets, plugin identifier, and plugin-specific options dictionary (e.g., `["variant":"reserved"]`).
- **[`Sources/ContainerPlugin/PluginLoader.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPlugin/PluginLoader.swift)**: Automatically discovers plugins placed in the user plugins directory and registers their launchd plists.

## Creating a Custom Network Plugin

To build a custom network plugin that integrates with the container runtime, you must reuse the existing scaffolding while providing your own implementation of the network logic.

### Step 1: Set Up the Swift Package Dependencies

Create a new Swift package that depends on the `Container*` modules, specifically `ContainerPlugin`, `ContainerNetworkServer`, and `ContainerNetworkVmnetServer`. These modules provide the protocols and types necessary to communicate with the container runtime.

### Step 2: Configure the Plugin Metadata

Provide a **PluginConfig** (either [`config.toml`](https://github.com/apple/container/blob/main/config.toml) or legacy [`config.json`](https://github.com/apple/container/blob/main/config.json)) that marks the plugin as a service by setting `services = true`. The `plugin` field should specify your custom identifier or reuse `"container-network-vmnet"` if extending the existing functionality. Place this configuration file adjacent to your binary.

### Step 3: Implement the Network Protocol

Depending on your requirements, copy and modify one of the two base implementations:

- For a simple NAT bridge, subclass or copy `AllocationOnlyVmnetNetwork` and adjust the default subnet or add logging.
- For full control over the network interface, copy `ReservedVmnetNetwork` and modify the `startNetwork` function to enable custom DHCP configurations, set specific MAC addresses, or implement firewall rules.

Your implementation must conform to the `Network` protocol and handle the `status`, `allocate`, and `lookup` routes via the XPC server.

### Step 4: Build the XPC Service Entry Point

Create a helper struct similar to `NetworkVmnetHelper` that parses command-line arguments and returns a `Network`-conforming object from its `createNetwork` function. This helper should start an XPC server (using `DefaultNetworkService`) that exposes the standard network routes to the container runtime.

## Working with NetworkConfiguration and Network Modes

The **`NetworkConfiguration`** struct serves as the central configuration object that carries all network-creation parameters. When parsing CLI arguments in your custom plugin, you must instantiate this object with the appropriate values:

```swift
let cfg = try NetworkConfiguration(
    name: id,
    mode: mode,  // .nat or .hostOnly
    ipv4Subnet: ipv4Subnet.map { try CIDRv4($0) },
    ipv6Subnet: ipv6Subnet.map { try CIDRv6($0) },
    plugin: "my-custom-plugin",
    options: ["variant":"reserved"]  // or "allocationOnly"
)

```

The **mode** parameter determines whether the network operates as a NAT bridge or host-only interface, while the **variant** option in the dictionary selects between the lightweight `allocationOnly` implementation and the full `reserved` vmnet reservation.

## Registering and Deploying Your Plugin

The **`PluginLoader`** automatically discovers plugins placed under the user plugins directory at `installRoot/libexec/container-plugins`. To register your custom network plugin:

1. Install your binary at `<plugin-dir>/bin/<plugin-name>`.
2. Place your configuration file ([`config.toml`](https://github.com/apple/container/blob/main/config.toml) or [`config.json`](https://github.com/apple/container/blob/main/config.json)) next to the binary.
3. Ensure the binary implements the service entry point and XPC server registration.

When the container runtime initializes, `PluginLoader` calls your factory method, creates a `Plugin` object, and registers the launchd plist via `PluginLoader.registerWithLaunchd`.

## Complete Implementation Example

The following Swift code demonstrates a complete custom network plugin entry point that reuses the `ReservedVmnetNetwork` implementation with custom configuration:

```swift
import ContainerPlugin
import ContainerNetworkServer
import ContainerNetworkVmnetServer
import ArgumentParser

// 1️⃣ Define the CLI entry point
@main
struct MyCustomNetwork: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "my-network-plugin",
        abstract: "Custom vmnet network plugin",
        subcommands: [Start.self]
    )
}

// 2️⃣ Parse arguments and build a NetworkConfiguration
extension MyCustomNetwork {
    struct Start: AsyncParsableCommand {
        @Option(name: .shortAndLong, help: "Network identifier")
        var id: String

        @Option(name: .long, help: "Network mode (nat | hostOnly)")
        var mode: NetworkMode = .nat

        @Option(name: .customLong("subnet"), help: "IPv4 CIDR")
        var ipv4Subnet: String?

        @Option(name: .customLong("subnet-v6"), help: "IPv6 CIDR")
        var ipv6Subnet: String?

        func run() async throws {
            let cfg = try NetworkConfiguration(
                name: id,
                mode: mode,
                ipv4Subnet: ipv4Subnet.map { try CIDRv4($0) },
                ipv6Subnet: ipv6Subnet.map { try CIDRv6($0) },
                plugin: "my-network-plugin",
                options: ["variant":"reserved"]
            )
            
            // Create a ReservedVmnetNetwork (or your own subclass)
            let network = try ReservedVmnetNetwork(configuration: cfg, log: ServiceLogger())
            try await network.start()
            
            // Set up XPC server as in NetworkVmnetHelper+Start
            // ... server registration code ...
        }
    }
}

```

## Summary

- **`container-network-vmnet`** provides two operation modes: `allocationOnly` for basic IPv4 NAT bridging on all macOS versions, and `reserved` for full IPv4/IPv6 vmnet control requiring macOS 26+.
- The plugin architecture relies on the **`NetworkVmnetHelper`** entry point, **`NetworkConfiguration`** for parameters, and **`ReservedVmnetNetwork`** or **`AllocationOnlyVmnetNetwork`** for implementation.
- Custom plugins must implement the `Network` protocol, expose an XPC service, and be installed in `installRoot/libexec/container-plugins` for automatic discovery by **`PluginLoader`**.
- You can extend existing functionality by copying the reference implementations in `Sources/Services/NetworkVmnet/Server/` and modifying the network setup logic while maintaining the standard XPC communication interface.

## Frequently Asked Questions

### What is the difference between `allocationOnly` and `reserved` modes in container-network-vmnet?

The **`allocationOnly`** mode creates a simple NAT bridge where the plugin only allocates an IPv4 subnet and reports it to the container runtime without using vmnet reservation APIs. This mode works on all supported macOS versions. The **`reserved`** mode uses the vmnet reservation APIs to create a fully-controlled network with both IPv4 and IPv6 support, maintains a `vmnet_network_ref`, and can expose the underlying XPC object to containers, but requires macOS 26 or later.

### How do I customize DHCP settings or MAC addresses in my network plugin?

To customize DHCP settings or MAC addresses, copy the **`ReservedVmnetNetwork`** implementation from [`Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift`](https://github.com/apple/container/blob/main/Sources/Services/NetworkVmnet/Server/ReservedVmnetNetwork.swift) and modify the `startNetwork` function. This function contains the vmnet C-API calls where you can configure custom DHCP ranges, set specific MAC addresses for interfaces, or add firewall rules before the network becomes active.

### Where does the container runtime look for custom network plugins?

The container runtime discovers plugins through the **`PluginLoader`** mechanism, which automatically searches the user plugins directory located at **`installRoot/libexec/container-plugins`**. Your plugin binary must be installed at `<plugin-dir>/bin/<plugin-name>` with its configuration file adjacent to it, and the loader will handle registration with launchd via `PluginLoader.registerWithLaunchd`.

### Can I use the existing container-network-vmnet binary with custom configuration instead of writing a new plugin?

Yes, you can reuse the existing `container-network-vmnet` binary by passing different `--variant`, `--subnet`, or `--subnet-v6` arguments to achieve different network configurations. However, if you need logic not supported by the stock implementation (such as custom DHCP servers or specific bridge layouts), you must create a new plugin that imports the `Container*` modules and provides a custom implementation of the `Network` protocol.