Skip to content

Make ios list discover tunnel/RSD-backed devices and support direct --address/--rsd-port targets - #807

Open
danielpaulus wants to merge 2 commits into
mainfrom
feature/issue-768-list-tunnel-backed-devices
Open

Make ios list discover tunnel/RSD-backed devices and support direct --address/--rsd-port targets#807
danielpaulus wants to merge 2 commits into
mainfrom
feature/issue-768-list-tunnel-backed-devices

Conversation

@danielpaulus

Copy link
Copy Markdown
Owner

Problem

ios list and device resolution are tied to local usbmuxd discovery. On a host that can reach an iOS 17+ RSD/userspace tunnel (e.g. a remote-device bridge where the phone is physically attached to another machine), ios list returns {"deviceList": []} and commands like rsd ls/info fail early with Device not found: <udid> — even when the user passes explicit --address/--rsd-port/--userspace-port coordinates that are perfectly reachable.

Design

Two additions, both on the CLI/resolution layer:

  1. Second discovery source for ios list. The list command now also queries the go-ios tunnel agent's existing /tunnels HTTP API (via the existing tunnel.ListRunningTunnels, honoring --tunnel-info-host/--tunnel-info-port/GO_IOS_AGENT_HOST/GO_IOS_AGENT_PORT) and merges tunnel-backed devices that usbmuxd does not report into the output. Merged entries carry a distinct transport marker: connectionType: "userspaceTunnel" (or "tunnel" for kernel TUN tunnels). usbmuxd entries win for devices known to both sources. ios list only hard-fails if both usbmuxd and the tunnel agent are unreachable. In --details mode, tunnel-only devices are listed with identity + transport instead of dying on the unreachable usbmuxd/lockdown path.

  2. Direct-target resolution. When --address and --rsd-port are supplied, a device missing from usbmuxd is no longer fatal: the DeviceEntry is built from the explicit coordinates, --userspace-port (+ --userspace-host) is preserved as the local forward endpoint, and --udid is treated as identity metadata. If no --udid is given, the UDID from the RSD handshake response is used. The RSD handshake itself is unchanged and still validated eagerly.

Implementation

  • cli_device_resolution.go
    • resolveDevice: the explicit --address/--rsd-port branch now runs before the Device not found exit; on usbmuxd miss it builds a directTargetDevice entry instead of failing.
    • New helpers: directTargetDevice, tunnelBackedDevices (queries /tunnels), tunnelBackedDeviceEntry, mergeTunnelDevices, isTunnelOnlyDevice, plus the connectionTypeTunnel/connectionTypeUserspaceTunnel markers.
  • main.go
    • printDeviceList(details, tunnelInfo): merges the tunnel source, tolerates a missing usbmuxd when the tunnel agent answers (warns instead of exiting).
    • detailsEntryForDevice: shared by JSON/no-JSON detailed output; skips lockdown GetValues for tunnel-only entries.
    • deviceWithRsdProvider: when GetDeviceWithAddress fails after a successful RSD handshake, keep the direct-target entry (attach the handshake's RsdPortProvider, backfill the UDID from the handshake) instead of exiting.
  • cmd_global.go: pass the tunnel-info config into the list command.
  • ios/tunnel/tunnel_api.go is untouched (read-side only via the existing ListRunningTunnels), deliberately avoiding overlap with the concurrent TunnelManager rework.

Options considered

  • Merge /tunnels into ios list + direct --address/--rsd-port target path (chosen). Reuses the tunnel agent API and the existing RSD plumbing (NewWithAddrPortDevice + Handshake), keeps changes additive and on the CLI layer, and matches what users expect (ios list shows reachable devices; explicit coordinates just work). This is also intentionally aligned with the Proposal: reuse Apple's CoreDevice tunnel on macOS instead of bringing up our own #713 Phase-1 direction: it's the same resolver-source plumbing (tunnel agent as a device source next to usbmuxd) that a future multi-source resolver can formalize, without committing to new library API now.
  • Separate specialist commands for RSD-only devices (e.g. ios rsd list). Works, but users would still hit the deviceList: [] / "Device not found" wall in normal commands; discoverability is poor.
  • Emulate usbmuxd from the bridge host. Heaviest option; duplicates discovery/forwarding logic go-ios already has for RSD and userspace tunnels.
  • Keep usbmuxd-only discovery (status quo): blocks remote/cloud workflows entirely.

Test plan

  • New unit tests in cli_device_resolution_test.go (device-free, run in CI) against a fake tunnel-agent HTTP server on 127.0.0.1:
    • /tunnels entries convert + merge into an empty usbmux list with correct udid, address, userspace TUN fields, and userspaceTunnel/tunnel transport markers.
    • Merge dedupes: usbmuxd entry wins for a shared UDID; tunnel-only entries are appended.
    • Direct-target resolution builds a usable entry from --address/--rsd-port with --udid as identity metadata (and empty UDID left for the RSD handshake to fill).
    • printDeviceList output contains the tunnel-only device.
    • --details output labels tunnel-only devices with the transport marker instead of failing.
  • go build ./..., go test ./... (all green), gofmt -l clean on changed files.
  • Real-device e2e suite via CI (/test-devices).

Fixes #768

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk

…ress targets

`ios list` now merges a second discovery source: the running go-ios tunnel
agent's /tunnels HTTP API. Devices that are reachable through a tunnel but
unknown to the local usbmuxd (e.g. remote-bridge setups) show up in the list
with a distinct transport marker (connectionType tunnel/userspaceTunnel), and
`ios list` no longer hard-fails when usbmuxd is unreachable as long as the
tunnel agent answers. In --details mode, tunnel-only devices are listed with
identity and transport instead of failing on the unreachable lockdown path.

Device resolution gains a direct-target path: when --address and --rsd-port
are given, a device missing from usbmuxd is no longer fatal. The entry is
built from the explicit coordinates (plus --userspace-port for userspace
tunnel forwards), --udid is treated as identity metadata, and the UDID is
taken from the RSD handshake when none was provided.

Fixes #768

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
@danielpaulus

Copy link
Copy Markdown
Owner Author

/test-devices

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Running real-device tests on PR #807run.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

❌ Real-device tests failed — see run.

Two follow-up fixes on the tunnel-backed device discovery path:

- mergeTunnelDevices only deduplicated tunnel entries against usbmuxd
  devices, so two /tunnels records with the same udid were both listed,
  and entries without a udid collapsed together. Track appended udids and
  drop udid-less tunnel entries, which cannot be addressed by identity.
- printDeviceList suppressed a usbmuxd failure whenever the tunnel agent
  merely responded, so `ios list` exited 0 with an empty list when both
  sources were empty. Only suppress the usbmuxd error when tunnel
  discovery actually produced devices; otherwise keep the original
  non-zero exit that scripts depend on.

Adds a merge-dedup unit test covering duplicate and udid-less entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
@danielpaulus

Copy link
Copy Markdown
Owner Author

Adversarial review (Claude + codex cross-check). Build, unit tests, go vet, and gofmt all clean.

Acceptance criteria (#768) — all met:

  • ios list includes tunnel-agent (/tunnels) devices even with no usbmuxd attachment — yes (mergeTunnelDevices).
  • Direct --address/--rsd-port target without usbmuxd resolution — yes (directTargetDevice + non-fatal GetDeviceWithAddress in deviceWithRsdProvider).
  • --udid as identity metadata, not a reason to fail — yes.
  • Transport visible in output — yes (connectionType: tunnel/userspaceTunnel).

JSON shape: No breaking change for scripts parsing ios list. Plain output stays {"deviceList":[udid,...]} (extra udids appended); --details keeps the same 5-field object shape (Product* empty for tunnel-only devices, which is intentional since lockdown is unreachable).

Fixes pushed (745c039):

  1. mergeTunnelDevices only deduped tunnel entries against usbmuxd, so duplicate /tunnels udids were listed twice and udid-less entries collapsed. Now tracks appended udids and drops udid-less tunnel entries. Added a unit test.
  2. printDeviceList suppressed a usbmuxd failure whenever the agent merely responded — ios list could exit 0 with an empty list when both sources were empty, dropping the non-zero exit scripts rely on. Now only suppresses the usbmux error when tunnel discovery actually produced devices.

Dismissed (with reasons):

  • --address without --rsd-port silently ignored: not a regression — --address alone was never a direct-target path, and both coords are required by design; strict validation risks the automatic-tunnel path.
  • --details fields empty for tunnel devices: intentional; lockdown is not reachable for tunnel-only devices, shape is preserved.
  • 5s /tunnels timeout on every ios list: it is a max, not a fixed delay — a missing local agent returns connection-refused immediately, and this matches the existing TunnelInfoForDevice pattern already used across commands.

No merge performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make ios list discover tunnel/RSD-backed devices

1 participant