How the vphoned Guest Daemon Communicates with the Host in vphone-cli
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) 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 numbert: 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 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 invphoned_hid.m) - File operations:
file_get,file_put(handled invphoned_files.m) - System services:
location,clipboard,keychain_add(handled invphoned_location.m,vphoned_clipboard.m,vphoned_keychain.m) - Application management:
apps,ipa_install(handled invphoned_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:
- JSON Header: The daemon sends a header containing
t(type),id, andsizefields - 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:
- The host sends an
updatecommand with the new binary size - The daemon invokes
receive_updateto stream the binary into a cache path - The daemon makes the new binary executable and exits
- launchd detects the exit and restarts the service from the cached binary
- 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:
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:
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:
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:
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →