# How the vphoned Guest Daemon Communicates with the Host in vphone-cli

> Learn how the vphoned guest daemon communicates with the host in vphone-cli. Discover its virtio-socket connection, JSON protocol, binary streaming, and automatic updates.

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

---

**The `vphoned` guest daemon communicates with the host client `VPhoneControl` over a virtio-socket (vsock) connection using a length-prefixed JSON protocol that supports request-response correlation, binary streaming, and automatic updates.**

The vphone-cli project enables control of iOS virtual machines through a guest-side agent that runs as a launch daemon inside the VM. This article examines the communication mechanism between the host-side Swift client and the guest-side Objective-C daemon, detailing the wire protocol, handshake process, and data transfer methods implemented in the Lakr233/vphone-cli repository.

## Communication Architecture

The communication channel relies on **virtio-socket (vsock)** technology to bridge the host and guest environments without traditional network overhead.

The host-side component **`VPhoneControl`** (defined in [`VPhoneControl.swift`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneControl.swift)) initiates connections to the guest, while the guest-side daemon **`vphoned`** (implemented in `vphoned.m`) listens on **vsock port 1337** and accepts incoming connections. This architecture provides a low-latency, hypervisor-native communication path that bypasses TCP/IP stack complexity.

## Protocol Specification

The vphone-control protocol uses a simple yet robust framing mechanism to ensure message boundaries and JSON integrity.

### Length-Prefixed JSON Framing

Every message follows the binary structure:

```

[uint32 big-endian length][UTF-8 JSON payload]

```

The JSON payload always contains two required fields:
- **`v`**: Protocol version number
- **`t`**: Message type identifier

An optional **`id`** field correlates requests with responses. The helper functions `vp_read_message` and `vp_write_message` in [`vphoned_protocol.h`](https://github.com/Lakr233/vphone-cli/blob/main/vphoned_protocol.h) and `vphoned_protocol.m` handle the low-level byte order conversion and buffer management.

### Handshake and Version Negotiation

Upon connection establishment, the host sends a **`hello`** message containing its binary SHA-256 hash. The daemon responds with its own capabilities, iOS version, IP address, and a `need_update` boolean flag.

If the hashes differ, the daemon sets `need_update` to `true`, triggering the auto-update mechanism before normal operation resumes.

## Request-Response Flow

After successful handshake, the host issues commands by sending JSON requests with unique `id` values. The daemon processes these in `handle_command` or dedicated `vp_handle_*` helper functions.

### Command Dispatch

The daemon supports diverse capabilities including:
- **HID input**: `hid`, `touch` (handled in `vphoned_hid.m`)
- **File operations**: `file_get`, `file_put` (handled in `vphoned_files.m`)
- **System services**: `location`, `clipboard`, `keychain_add` (handled in `vphoned_location.m`, `vphoned_clipboard.m`, `vphoned_keychain.m`)
- **Application management**: `apps`, `ipa_install` (handled in `vphoned_apps.m`)

Each handler returns a response dictionary constructed via `vp_make_response`, which includes the original request `id` for correlation.

### Binary Data Streaming

For commands involving binary payloads—such as file transfers, clipboard images, or firmware updates—the protocol switches to a two-phase transfer:

1. **JSON Header**: The daemon sends a header containing `t` (type), `id`, and `size` fields
2. **Raw Bytes**: The actual binary data streams directly on the same socket immediately after the JSON header

The host reads the size from the header, then consumes exactly that number of bytes from the socket buffer.

## Auto-Update Mechanism

The daemon maintains self-update capability through hash comparison during the initial handshake.

When the host hash differs from the daemon's executable hash:
1. The host sends an **`update`** command with the new binary size
2. The daemon invokes `receive_update` to stream the binary into a cache path
3. The daemon makes the new binary executable and exits
4. **launchd** detects the exit and restarts the service from the cached binary
5. The new instance reconnects to the host with matching hashes

This ensures the guest agent stays synchronized with the host client version without manual VM intervention.

## Reconnection Logic

Connection resilience is implemented on both sides of the socket.

The host-side `VPhoneControl` monitors the connection state; if the socket drops or the handshake times out, it schedules reconnection attempts after a short delay. On the guest side, after handling a client connection, the daemon loops back to `accept` and waits for the next host connection, ensuring the VM remains controllable across host application restarts.

## Code Implementation Examples

### Host-Side Request (Swift)

Sending a HID key press through the `VPhoneControl` API:

```swift
let control = VPhoneControl(variant: .regular)
await control.sendHIDPress(page: 0x0C, usage: 0x00)   // Home button simulation

```

### Guest-Side Handling (Objective-C)

Processing an incoming `hid` command in the daemon's dispatch logic:

```objc
if ([type isEqualToString:@"hid"]) {
    uint32_t page  = [msg[@"page"] unsignedIntValue];
    uint32_t usage = [msg[@"usage"] unsignedIntValue];
    NSNumber *downVal = msg[@"down"];
    if (downVal != nil) {
        vp_hid_key(page, usage, [downVal boolValue]);
    } else {
        vp_hid_press(page, usage);
    }
    return vp_make_response(@"ok", reqId);
}

```

### File Transfer Implementation

Uploading a file from host to guest:

```swift
let data = try Data(contentsOf: localURL)
await control.uploadFile(path: "/tmp/example.bin", data: data)

```

Receiving the file on the guest side after the `file_put` header:

```objc
if ([type isEqualToString:@"file_put"]) {
    NSUInteger size = [msg[@"size"] unsignedIntegerValue];
    // Read `size` bytes directly from socket after JSON header
    NSData *fileData = [socket readDataOfLength:size];
    // Write to specified path...
    return vp_make_response(@"ok", reqId);
}

```

## Summary

- **Transport**: virtio-socket (vsock) on port 1337 provides the communication channel between host and guest
- **Framing**: Length-prefixed JSON (`vp_read_message`, `vp_write_message`) ensures reliable message parsing
- **Handshake**: SHA-256 hash exchange enables automatic version detection and binary updates
- **Binary Streaming**: Two-phase protocol (JSON header + raw bytes) handles file transfers and large payloads
- **Resilience**: Automatic reconnection logic on both sides maintains persistent control across network interruptions
- **Modularity**: Feature-specific handlers (`vphoned_hid.m`, `vphoned_files.m`, etc.) isolate capabilities for maintainability

## Frequently Asked Questions

### What transport protocol does vphoned use to communicate with the host?

The daemon uses **virtio-socket (vsock)**, a hypervisor-native socket interface that allows communication between host and guest without traditional network configuration. According to the vphone-cli source code, `vphoned` listens on vsock port 1337 while the host-side `VPhoneControl` initiates the connection.

### How does the protocol handle binary data like images or files?

Binary data transfers use a two-phase approach: first, a JSON header containing the payload size is sent using the standard length-prefixed format, then the raw bytes stream directly on the same socket. The receiver reads the exact byte count specified in the header before resuming JSON message processing.

### What happens if the host and guest versions mismatch?

During the handshake, the daemon compares its executable SHA-256 hash with the host's hash. If they differ, the host sends an `update` command and streams the new binary to the guest. The daemon writes this to a cache location, makes it executable, and exits. Launchd then restarts the service from the updated binary, completing the seamless update without manual intervention.

### How does the host correlate responses with requests?

Every request includes a unique **`id`** field in the JSON payload. When `vphoned` processes a command in `handle_command` or its specialized helpers like `vp_handle_file_command`, it returns this same `id` in the response dictionary created by `vp_make_response`. The host-side Swift code uses this identifier to match asynchronous responses with their original requests.