# How vphone-cli Communicates with the Guest Daemon (vphoned): Inside the vsock JSON Protocol

> Discover how vphone-cli talks to vphoned via vsock and JSON. Learn about its request-response correlation, binary updates, and reconnection.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: internals
- Published: 2026-09-05

---

**vphone-cli communicates with the guest-side daemon vphoned over a vsock (virtual socket) connection on port 1337, using a length-prefixed JSON protocol that supports request-response correlation, automatic binary updates, and robust reconnection handling.**

The `Lakr233/vphone-cli` project enables host control of iOS virtual machines by establishing a high-speed communication channel between the host CLI tool and the guest daemon. Understanding how vphone-cli communicates with the guest daemon vphoned requires examining the virtual socket implementation, message framing architecture, and command dispatch logic defined in the Swift source code. This deep dive reveals a carefully designed protocol that balances performance with reliability.

## The vsock Connection Architecture

At the transport layer, vphone-cli utilizes **Apple's Virtualization framework** to establish a vsock connection between the host and guest environments. The host-side client logic resides in **[`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)**, which manages the lifecycle of the virtual socket connection.

The connection initiates through the **`connect(device:)`** method, which accepts a `VZVirtioSocketDevice` instance representing the virtual machine's virtio socket hardware. This method stores the device reference and immediately invokes **`attemptConnect()`** to establish a TCP stream to the guest. The implementation targets a fixed port **1337** (defined as `Self.vsockPort`), where the `vphoned` daemon listens inside the iOS guest.

## The Length-Prefixed JSON Protocol

All communication between vphone-cli and vphoned follows a strict **binary framing format**: **`[uint32 big-endian length][UTF-8 JSON payload]`**. This framing ensures message boundaries are preserved over the stream socket without requiring complex delimiters.

The low-level I/O operations are handled by helper methods in [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift):
- **`writeMessage(fd:dict:)`** serializes the dictionary to JSON, calculates the byte length, and prepends the 4-byte big-endian length header before writing to the socket file descriptor.
- **`readMessage(fd:)`** performs the inverse operation, first reading 4 bytes to determine payload size, then reading the exact JSON bytes.

Every JSON message includes a `"t"` (type) field identifying the command (touch injection, file transfer, clipboard operations, etc.) and a unique **request ID** for responses that require correlation.

## Connection Lifecycle and Handshake

### Opening the vsock Channel

The connection establishment follows a deterministic sequence. When **`attemptConnect()`** successfully creates a socket file descriptor, it immediately triggers **`performHandshake(fd:attemptToken:)`**. This handshake validates protocol compatibility before allowing general command traffic.

### Capability Exchange

During the handshake phase, vphone-cli transmits a **hello** JSON message containing the protocol version and, when a signed `vphoned` binary is provided on the host, its **SHA-256 hash**. The guest daemon responds with critical metadata including its name, capabilities bitmask, assigned IP address, iOS version string, and a boolean `need_update` flag indicating whether the guest binary requires replacement.

This exchange enables the host to adapt its command set based on guest capabilities and determines whether automatic updates are necessary.

### Auto-Update Mechanism

If the handshake reveals `need_update` is true and the host possesses a signed binary, **`pushUpdate(fd:)`** initiates a streaming transfer. This method sends an `update` header message followed by the binary data, allowing the guest daemon to hot-reload without restarting the virtual machine.

## Command Dispatch and the Read Loop

Once the handshake completes, **`startReadLoop(fd:attemptToken:)`** enters a continuous monitoring state. This read loop calls **`readMessage(fd:)`** to process incoming length-prefixed JSON messages, dispatching them to one of two handlers:

- **Request responses**: Matched against the `pendingRequests` dictionary using the request ID and delivered to the awaiting Swift concurrency continuation.
- **Fire-and-forget messages**: Simple acknowledgments like `pong` or `ok` that require no response correlation.

For outgoing commands, **`sendRequest(_:)`** generates a unique UUID, stores the continuation in `pendingRequests`, arms a timeout timer, and transmits the payload via `writeMessage(fd:dict:)`. This architecture supports fully asynchronous command execution while maintaining strict request-response pairing.

## Resilience and Reconnection Strategy

The protocol includes robust error handling through **`disconnect(ifCurrentAttempt:)`**. When the socket closes unexpectedly or a command timeout occurs, this method:
1. Invalidates the current socket file descriptor
2. Fails all pending requests with timeout errors
3. Schedules a reconnection attempt after `Self.reconnectDelay` seconds

This automatic reconnection logic ensures that transient network interruptions or guest daemon restarts do not permanently sever the host's control over the virtual machine.

## Practical Implementation Examples

The following Swift patterns demonstrate how to instantiate the control client and execute common operations against the guest daemon:

```swift
// Create the control client and attach it to the VM's vsock device
let control = VPhoneControl(variant: .regular)
control.onConnect = { caps in
    print("Guest capabilities:", caps)
}
control.connect(device: vsockDevice)   // vsockDevice is a VZVirtioSocketDevice

// Send a simple ping to verify connectivity
Task {
    try await control.sendPing()
}

// Upload a file to the guest filesystem
Task {
    let localURL = URL(fileURLWithPath: "/tmp/example.txt")
    let data = try Data(contentsOf: localURL)
    try await control.uploadFile(path: "/tmp/example.txt", data: data)
}

// Inject touch events at normalized coordinates (0.0 to 1.0)
control.sendTouch(phase: 0, x: 0.5, y: 0.5)   // Touch down at screen center

```

## Summary

- **vphone-cli communicates with vphoned** over a vsock connection on **port 1337**, utilizing `VZVirtioSocketDevice` from Apple's Virtualization framework.
- The protocol employs **length-prefixed JSON framing** with a 4-byte big-endian length header followed by UTF-8 encoded JSON payloads.
- A **handshake sequence** exchanges protocol versions, capabilities, and update requirements before general command traffic begins.
- **Request-response correlation** uses unique identifiers stored in a `pendingRequests` dictionary to match asynchronous replies.
- **Automatic binary updates** stream new `vphoned` versions to the guest when the `need_update` flag is set during handshake.
- **Reconnection logic** with configurable delays ensures resilience against connection drops without manual intervention.

## Frequently Asked Questions

### What transport protocol does vphone-cli use to communicate with the guest daemon?

vphone-cli uses **vsock** (virtual socket) provided by the `VZVirtioSocketDevice` in Apple's Virtualization framework. The connection targets port 1337 inside the guest where the `vphoned` daemon listens, creating a high-speed, host-to-guest communication channel that bypasses traditional networking stacks.

### How are messages formatted between vphone-cli and vphoned?

Messages use a **length-prefixed JSON format**: each message begins with a 4-byte big-endian unsigned integer specifying the JSON payload length, followed immediately by the UTF-8 encoded JSON data. This framing mechanism, implemented in `writeMessage(fd:dict:)` and `readMessage(fd:)`, ensures reliable message boundary detection over the stream-based vsock connection.

### What happens during the initial connection handshake?

The handshake involves `performHandshake(fd:attemptToken:)` sending a **hello** message containing the protocol version and optionally a SHA-256 hash of a signed `vphoned` binary. The guest daemon responds with its capabilities, IP address, iOS version, and a `need_update` boolean. This exchange determines protocol compatibility and whether the host should initiate an automatic binary update before accepting further commands.

### How does vphone-cli handle connection failures?

When the vsock closes or a command times out, `disconnect(ifCurrentAttempt:)` performs cleanup by failing pending requests and invalidating the socket. The system then schedules an automatic reconnection attempt after `Self.reconnectDelay` seconds, allowing the host to recover from guest daemon restarts or transient virtualization framework interruptions without user intervention.