How vphone-cli Communicates with the Guest Daemon (vphoned): Inside the vsock JSON Protocol
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, 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:
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
pendingRequestsdictionary using the request ID and delivered to the awaiting Swift concurrency continuation. - Fire-and-forget messages: Simple acknowledgments like
pongorokthat 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:
- Invalidates the current socket file descriptor
- Fails all pending requests with timeout errors
- Schedules a reconnection attempt after
Self.reconnectDelayseconds
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:
// 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
VZVirtioSocketDevicefrom 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
pendingRequestsdictionary to match asynchronous replies. - Automatic binary updates stream new
vphonedversions to the guest when theneed_updateflag 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.
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 →