How the Host CLI Communicates with the Guest Daemon in vphone-cli
The host CLI (vphone-cli) communicates with the guest vphoned daemon over a virtual socket (vsock) using a length-prefixed JSON protocol on port 1337, with automatic handshake, request-response tracking, and binary auto-update capabilities.
The vphone-cli project provides a command-line interface for managing iOS virtual machines on macOS through Apple's Virtualization framework. The host-side communication architecture centers on a clean separation between the VM lifecycle management and the control channel that talks to the guest daemon running inside the iOS VM.
Virtual Socket (vsock) Setup and Connection
The communication channel starts with VM configuration in VPhoneVirtualMachine.swift. When building the VZVirtualMachineConfiguration, the code adds a VZVirtioSocketDeviceConfiguration to the socket devices array:
// VPhoneVirtualMachine.swift around line 246
let socketDevice = VZVirtioSocketDeviceConfiguration()
configuration.socketDevices = [socketDevice]
Once the VM is running, VPhoneAppDelegate.swift retrieves the actual VZVirtioSocketDevice from the VM's socketDevices and passes it to VPhoneControl. The connect(device:) method initiates the vsock connection:
func connect(device: VZVirtioSocketDevice) {
// Stores weak reference: private weak var device: VZVirtioSocketDevice?
device.connect(toPort: vsockPort) // vsockPort = 1337
}
The connection result is stored as a VZVirtioSocketConnection in private var connection: VZVirtioSocketConnection?.
Handshake and Protocol Negotiation
After the TCP-like socket connects, VPhoneControl performs a mandatory handshake. The host sends a JSON "hello" message containing:
"v"— protocol version"t"— message type (defined in lines 10-12 ofVPhoneControl.swift)- Optional SHA-256 hash of the signed
vphonedbinary
The message format uses a 4-byte big-endian length prefix followed by UTF-8 JSON:
[uint32 length][UTF-8 JSON payload]
The guest responds with its name, capabilities, IP address, iOS version, and a need_update flag. This response enables capability negotiation — the host checks the capabilities list (e.g., "touch", "accessibility_tree") to determine which features to enable, such as useGuestTouchInjection.
If need_update is true, the host initiates an auto-update before proceeding with normal operations.
Length-Prefixed JSON Protocol
All subsequent communication follows the same framing discipline implemented in writeMessage(fd:dict:) and readMessage(fd:):
- Writing: Serialize JSON, prepend 4-byte big-endian length, write to socket
- Reading: Read 4 bytes as length, then read exactly that many bytes as JSON payload
This design supports both fire-and-forget commands and full request-response flows.
Request-Response Flow with Async/Await
For commands requiring replies, VPhoneControl implements a complete async request-response system:
- Request generation:
sendRequest(_:)generates a uniquenextRequestId, stores a callback inpendingRequests, arms a timeout timer, and writes the length-prefixed JSON - Response matching:
startReadLoop(fd:attemptToken:)runs on a background thread, parsing incoming messages and matching them to pending request IDs - Callback delivery: Matched responses invoke their stored closures on the main queue
- Binary payloads: Responses may include inline binary data (file contents, clipboard images) read via
readFully(fd:buf:count:)after the JSON header
// Example: Ping with automatic request-response handling
Task {
try await control.sendPing() // Generates request ID, awaits response
}
Auto-Update Mechanism
When the guest reports need_update, the host streams the signed vphoned binary through the same socket channel:
- Host sends special
updateJSON header viapushUpdate(fd:) - Host writes raw binary bytes of the new
vphonedexecutable - Guest acknowledges update completion
- Host resumes
startReadLoopfor normal operation
This ensures the guest daemon stays synchronized with the host CLI version without manual intervention.
Reconnection and Error Handling
On connection failure, scheduleReconnect delays and retries automatically. All pending requests in pendingRequests are failed with ControlError.notConnected to prevent memory leaks and hanging async operations.
Practical Usage Examples
import VPhoneCore
// Initialize control for a VM variant
let control = VPhoneControl(variant: .regular)
// Connect to running VM's vsock device
if let socket = vm.virtualMachine.socketDevices.first as? VZVirtioSocketDevice {
control.connect(device: socket) // Triggers handshake + read loop
}
// Asynchronous commands with automatic response handling
Task {
// Simple ping to verify connectivity
try await control.sendPing()
// List guest filesystem
let entries = try await control.listFiles(path: "/var/mobile/Documents")
// Install IPA with automatic vphoned update if needed
let result = try await control.installIPA(
localURL: URL(fileURLWithPath: "/path/to/app.ipa")
)
}
// Fire-and-forget touch injection (no response expected)
control.sendTouch(phase: 0, x: 0.5, y: 0.3) // touch down
control.sendTouch(phase: 1, x: 0.6, y: 0.4) // touch move
control.sendTouch(phase: 3, x: 0.6, y: 0.4) // touch up
Key Source Files
| File | Purpose |
|---|---|
VPhoneControl.swift |
Core client: socket handling, handshake, sendRequest(_:), startReadLoop(fd:attemptToken:), auto-update |
VPhoneVirtualMachine.swift |
VM configuration, VZVirtioSocketDeviceConfiguration setup around line 246 |
VPhoneAppDelegate.swift |
Bridges running VM to VPhoneControl.connect(device:) |
VPhoneCameraServer.swift |
Secondary component using same vsock channel for camera streaming |
research/VPhoneVirtualMachineRefactored.swift |
Design documentation for vsock architecture |
Summary
- Transport: Virtual socket (vsock) via
VZVirtioSocketDeviceon port 1337 - Framing: 4-byte big-endian length prefix + UTF-8 JSON payload
- Handshake: Version exchange, capability negotiation, SHA-256 hash verification
- Pattern: Async request-response with unique IDs, timeouts, and background read loop
- Features: Automatic
vphonedbinary updates, binary payload streaming, graceful reconnection - Entry point:
VPhoneControl.connect(device:)inVPhoneControl.swift
Frequently Asked Questions
What port does vphone-cli use for vsock communication?
The host CLI uses port 1337 defined as vsockPort in VPhoneControl.swift. This is passed to device.connect(toPort: 1337) when establishing the VZVirtioSocketConnection.
How does vphone-cli handle binary data transfer over the vsock?
Binary data follows the JSON header in the same connection. After parsing the JSON response, the code calls readFully(fd:buf:count:) to read the exact byte count specified in the message. This is used for file transfers, clipboard images, and the vphoned auto-update mechanism.
What happens if the guest vphoned version mismatches the host?
During handshake, the guest sends a need_update flag if its binary hash differs from the host's expected value. The host then enters pushUpdate(fd:), streams the signed binary over the socket, waits for acknowledgment, and resumes normal operation without restarting the VM.
Can multiple commands run simultaneously over the same vsock?
Yes. The sendRequest(_:) method generates unique request IDs and stores callbacks in pendingRequests. The background read loop matches incoming responses to pending requests by ID, allowing concurrent async operations without blocking the socket.
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 →