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 of VPhoneControl.swift)
  • Optional SHA-256 hash of the signed vphoned binary

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:

  1. Request generation: sendRequest(_:) generates a unique nextRequestId, stores a callback in pendingRequests, arms a timeout timer, and writes the length-prefixed JSON
  2. Response matching: startReadLoop(fd:attemptToken:) runs on a background thread, parsing incoming messages and matching them to pending request IDs
  3. Callback delivery: Matched responses invoke their stored closures on the main queue
  4. 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:

  1. Host sends special update JSON header via pushUpdate(fd:)
  2. Host writes raw binary bytes of the new vphoned executable
  3. Guest acknowledges update completion
  4. Host resumes startReadLoop for 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 VZVirtioSocketDevice on 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 vphoned binary updates, binary payload streaming, graceful reconnection
  • Entry point: VPhoneControl.connect(device:) in VPhoneControl.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →