# How XPC Communication Works Between Container Services in apple/container

> Discover how XPC communication works in apple/container. Learn about the custom RPC layer, XPCServer and XPCClient components, and built-in authentication for seamless container service interaction.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: internals
- Published: 2026-06-24

---

**The apple/container framework implements a custom RPC layer on top of macOS XPC, using dictionary-based messages routed between `XPCServer` and `XPCClient` components with built-in authentication and error handling.**

The apple/container repository provides a containerization framework for macOS that relies on secure inter-process communication to coordinate services. Understanding how XPC communication between container services functions requires examining the lightweight RPC implementation built atop Mach service bindings. The architecture separates concerns into distinct server-side listeners and client-side connection managers, with shared message types handling payload serialization and routing across process boundaries.

## Server-Side Architecture (XPCServer)

The server implementation in [`Sources/ContainerXPC/XPCServer.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCServer.swift) establishes a Mach service listener and manages the lifecycle of incoming client connections through a routing table.

### Listener Creation and Mach Service Setup

The `XPCServer` class initializes a Mach service using `xpc_connection_create_mach_service` to create a named endpoint that clients can discover. This listener runs asynchronously, yielding new connection objects as clients connect to the service identifier.

### Connection Acceptance and Session Management

Each incoming `xpc_connection_t` spawns a new `XPCServerSession` (defined in [`Sources/ContainerXPC/XPCServerSession.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCServerSession.swift)). This session tracks per-client state and manages **disconnect handlers** registered via `session.onDisconnect(_:)`. The session wraps the raw XPC connection in an `AsyncStream<xpc_object_t>` to integrate with Swift concurrency.

### Message Routing and Handler Dispatch

Incoming messages are interpreted as dictionaries. The server extracts the **route** key (`XPCMessage.routeKey`) and performs a lookup in the route table (`[String: RouteHandler]`). Each registered handler receives the `XPCMessage` object and returns a reply dictionary that the server sends back to the client.

### Security Validation via Audit Tokens

Before processing requests, the server validates the client's effective UID using `audit_token_to_euid` to ensure it matches the server's UID. This check prevents privilege escalation attacks where a malicious client might attempt to access container services from a different security context.

### Error Serialization and Disconnect Handling

If a route handler throws a `ContainerizationError`, the server serializes it into a `ContainerXPCError` structure and includes it in the reply under the `com.apple.container.xpc.error` key. Generic errors receive similar wrapping. When connections terminate, `XPCServerSession.fireDisconnect()` invokes all registered cleanup handlers.

## Client-Side Architecture (XPCClient)

The client implementation in [`Sources/ContainerXPC/XPCClient.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCClient.swift) handles connection establishment, request marshaling, and response parsing.

### Connection Setup and Mach Service Creation

`XPCClient` creates a connection to the server's Mach service using `xpc_connection_create_mach_service`. This establishes the underlying transport channel that persists across multiple requests.

### Request Sending and Timeout Handling

The `send(_:responseTimeout:)` method marshals an `XPCMessage` into an XPC dictionary and transmits it using `xpc_connection_send_message_with_reply`. The implementation sets up a task group that waits for either the server reply or a timeout, converting XPC-level failures into `ContainerizationError` instances via the `parseReply` helper.

### Persistent Sessions and Disconnect Monitoring

Clients call `openSession()` to obtain an `XPCClientSession` (from [`Sources/ContainerXPC/XPCClientSession.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCClientSession.swift)). This session installs a disconnect handler immediately, ensuring that server crashes or unexpected terminations are never missed. Additional cleanup handlers may be registered via `session.onDisconnect(_:)`.

## Message Format and Protocol (XPCMessage)

The `XPCMessage` type in [`Sources/ContainerXPC/XPCMessage.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCMessage.swift) defines the wire protocol used by both ends of the connection.

### Dictionary Structure and Routing

Messages are XPC dictionaries containing mandatory fields:

- `com.apple.container.xpc.route` — The RPC endpoint name used for handler lookup
- `com.apple.container.xpc.error` — Optional JSON-encoded `ContainerXPCError` for failure responses

### Typed Accessors and Error Propagation

The class provides typed accessors for extracting data, strings, booleans, integers, dates, file handles, and XPC endpoints from the dictionary. Errors serialize as `ContainerXPCError` (containing code and message fields) and deserialize back into `ContainerizationError` on the receiving side, preserving type information across the process boundary.

## Implementation Example

### Creating a Service with Route Handlers

```swift
import Logging
import ContainerXPC

let log = Logger(label: "com.apple.container.example")
let routes: [String: XPCServer.RouteHandler] = [
    "ping": XPCServer.route { msg in
        var reply = msg.reply()
        reply.set(key: "result", value: "pong")
        return reply
    },
    "status": XPCServer.route { msg in
        // Gather container status...
        var reply = msg.reply()
        reply.set(key: "running", value: true)
        return reply
    }
]

let server = XPCServer(identifier: "com.apple.container.example",
                      routes: routes,
                      log: log)

try await server.listen()

```

### Sending Requests from a Client

```swift
import ContainerXPC

let client = XPCClient(service: "com.apple.container.example")
let session = client.openSession()

var request = XPCMessage(route: "ping")
let response = try await session.send(request)

if let result = response.string(key: "result") {
    print("Server replied: \(result)")   // → "Server replied: pong"
}

```

## Summary

- **Server Architecture**: [`Sources/ContainerXPC/XPCServer.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCServer.swift) creates Mach service listeners using `xpc_connection_create_mach_service`, validates client identity via `audit_token_to_euid`, and dispatches to handlers through a route table keyed by `XPCMessage.routeKey`.
- **Client Architecture**: [`Sources/ContainerXPC/XPCClient.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCClient.swift) establishes connections and sends messages with timeout handling, while `XPCClientSession` manages persistent state and disconnect detection.
- **Message Protocol**: [`Sources/ContainerXPC/XPCMessage.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCMessage.swift) defines dictionary-based messages with the `com.apple.container.xpc.route` field for routing and `com.apple.container.xpc.error` for serialized `ContainerXPCError` payloads.
- **Error Handling**: Both sides convert between `ContainerizationError` and `ContainerXPCError` to maintain type safety across the XPC boundary.
- **Session Management**: Per-client state tracking occurs in `XPCServerSession` and `XPCClientSession`, with support for `onDisconnect` handlers on both ends.

## Frequently Asked Questions

### How does the XPCServer validate client identity?

The server extracts the audit token from the incoming XPC connection and calls `audit_token_to_euid` to obtain the client's effective UID. According to the implementation in [`Sources/ContainerXPC/XPCServer.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCServer.swift), the server compares this UID against its own process UID and rejects connections that do not match, preventing privilege escalation between different user contexts.

### What happens when a container service crashes or disconnects?

Both client and server implement disconnect handling through session objects. On the client side, `XPCClientSession` (in [`Sources/ContainerXPC/XPCClientSession.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCClientSession.swift)) installs a disconnect handler immediately upon creation, ensuring that server crashes trigger cleanup callbacks. The server uses `XPCServerSession.fireDisconnect()` to notify registered handlers when a client connection terminates.

### How are errors propagated between client and server?

Errors thrown by route handlers are caught by the server and serialized into `ContainerXPCError` structures, which are encoded as JSON and placed in the `com.apple.container.xpc.error` field of the reply dictionary. The client's `parseReply` method deserializes these back into `ContainerizationError` instances, preserving error codes and messages across the process boundary.

### Why does apple/container use a custom RPC layer instead of standard XPC?

The framework implements a custom RPC layer to provide type-safe message routing, container-specific error serialization, and structured session management. While built atop `xpc_connection_create_mach_service`, the abstraction adds mandatory UID validation, timeout handling for requests, and clean integration with Swift concurrency through `AsyncStream`, which raw XPC APIs do not provide directly.