Skip to content

S2.B — Pair attempt log (Phase B deliverable)

Status: verified — empirical smoke test

Every observation in this log is backed by a raw artifact captured during the session-10 Pair-button smoke test. Conclusions about Option α/γ falsification and the move to Option δ flow directly from those observations.

Date: session 10, post Stage 2.A commit 18331ca Status: Phase B complete, outcome classified, root cause identified

Setup

  • Stage 2.A landed: three DeviceInfo lies removed (pairingState=.paired, areDDI=true, preparednessState=.all).
  • state=.connected kept (and re-added after initial Phase A revert caused Xcode to filter the device out) — it is a truthful link-state value because an RSDDeviceWrapper with a live tunnel exists.
  • Restored env via iosmux-restore.sh: host NCM bridge up, havoc-side pymobiledevice3 remote tunneld running on 127.0.0.1:49151, tunnel endpoint [fdc5:8480:2949::1]:57346 advertised.
  • Fresh inject deployed to /Library/Developer/CoreDevice/, CDS restarted. Xcode Devices window opened: device row visible with Pair button, Copy Identifier returns <unknown> (expected for an unpaired device with no UUID surfaced to the user), no error banners.

Click event

User clicked Pair in Xcode Devices window. Observed:

  • iPhone row briefly disappeared and reappeared in the Xcode list.
  • Physical iPhone displayed no "Trust This Computer?" prompt.
  • No obvious error in Xcode UI.

Logs captured

  • /tmp/s2b_cds.loglog stream predicate='process == "CoreDeviceService"'
  • /tmp/s2b_xcode.loglog stream predicate='process == "Xcode"'
  • /tmp/s2b_remotepairing.loglog stream subsystem='com.apple.remotepairing'
  • /tmp/iosmux_inject.log — inject-owned log
  • /tmp/iosmux_md_proxy.log — md_proxy interposer log
  • /tmp/iosmux-tunneld.log — tunneld-side log

Key observations

O1. PairActionImplementation reached CDS

14:40:38.513 Df CoreDeviceService[7268] [com.apple.dt.coredevice:action]
    invoke(usingContentsOf:): Invoking action
    (type=PairActionImplementation,
     invocation=39A67811-B825-4DE4-B458-1BE7C0871909,
     device=E8A190DD-64F5-44A4-8D57-28E99E316D60)

The XPC envelope from Xcode made it into CDS's action dispatcher via our S1.B passthrough trampoline at CDS+0xB896. No SIGSEGV. Our hooks are passively observing, not intercepting, the pair path.

O2. Our inject is not on the pair code path

After click, the only lines in /tmp/iosmux_inject.log are from CDS re-init (new process 7506 spawned, installing hooks fresh). Zero new log lines from any of our DYLD_INTERPOSE hooks (remote_service_create_connected_socket, remote_service_connect_socket, xpc_remote_connection_create_with_remote_service). CDS's pair path does not flow through the MobileDevice/RemoteServiceDiscovery API we currently interpose.

O3. CDS opens nw_connection directly to the tunnel endpoint

CDS (PID 7273) opens two nw_connections on utun4 scoped interface:

C2: local fdc5:8480:2949::2.51128 → remote [fdc5:8480:2949::1]:57346  tcp / http2_transport
C3: same quartet, retry                                              tcp / http2_transport

Both receive Connection reset by peer immediately:

14:40:38.872 nw_flow_error [C2 ... utun4 ...] Output protocol (http2_transport)
             sent error: Connection reset by peer
14:40:38.878 nw_flow_error [C3 ... utun4 ...] Output protocol (http2_transport)
             sent error: Connection reset by peer
14:40:38.881 ... Broken pipe

O4. com.apple.remotepairing subsystem had zero events

/tmp/s2b_remotepairing.log file size = 70 bytes (header only). CDS never reached the code path that would have emitted events to this subsystem, because the base HTTP/2 transport to the RSD endpoint was reset before any higher-layer pairing protocol could start.

O5. RemoteServiceDiscovery lookups also fail

14:40:40.001 Failed to start service via RemoteServiceDiscovery:
             0xe800000b (kAMDNotConnectedError)
14:40:40.005 Failed to start service via RemoteServiceDiscovery:
             0xe800000b (kAMDNotConnectedError)
14:40:40.018 Failed to look up available remote services.
14:40:40.019 Error fetching developer mode status from device:
             Could not connect to the device.

Same root cause — the underlying transport can't establish, so every service lookup fails.

O6. No CDS crash

Three distinct CDS PIDs present in the log (7268, 7273, 7506). Two of them (7273, 7506) are still alive after the click window. PID 7268 logged exactly one line (the Invoking action above) and then stopped logging — consistent with being a short-lived action-host process that exited after reporting failure to Xcode, not with a SIGSEGV. No crash report in /Library/Logs/DiagnosticReports/ or the per-user equivalent for today.

O7. The device survives in devicectl

After the failed click:

$ devicectl list devices
iPhone (iosmux)   ...   connected (no DDI)   iPhone SE (3rd generation)

state is connected (no DDI) — this is the honest post-A output. Before S2.A it would have been connected (from our forced tag). The (no DDI) annotation is CDS's own subjective note on the preparedness state it defaults to when we don't force it.

Ground-truth verification of the tunnel

Key sanity check to rule out "the tunnel itself is broken":

$ pymobiledevice3 remote rsd-info
{
  "MessageType": "Handshake",
  "MessagingProtocolVersion": 7,
  "Properties": { "BuildVersion": "23E254", ... full handshake ... }
}

$ pymobiledevice3 mounter list --tunnel 00008110-0004596E22A0401E
[]

$ pymobiledevice3 developer dvt ls / --tunnel 00008110-0004596E22A0401E
/usr
/.resolve
/bin
/sbin
/etc
/System
/var
/Library
/private
/Applications
...

A Python client using the same havoc-side tunneld and the same RSD endpoint [fdc5:8480:2949::1]:57346:

  • successfully pulls a full handshake,
  • successfully queries DDI mount state (empty list is a real answer, not an error),
  • successfully lists the iPhone's root filesystem via the DVT developer service.

In addition, the iosmux_md_proxy interposer continues to serve retrievePropertiesForUUID correctly from the in-memory handshake table. This has been observed working across many prior sessions.

Classification and diagnosis

Outcome maps to neither a clean A/B/C/D/E from the roadmap, but closest to C ("pair request reaches CDS but dies somewhere on the way to the iPhone") — with one critical refinement:

The transport-level failure is not a routing bug. It is a client-protocol mismatch.

  • The tunnel is functional. Confirmed by two independent workloads (mounter query, DVT filesystem listing) on the same endpoint.
  • The iPhone answers its side of the wire correctly to a caller that speaks the right protocol.
  • CDS does not speak that protocol. Apple's native CDS was written to talk to a transport produced by Apple's own usbmuxd / mobiledeviced / remoted stack, which speaks a different dialect on top of the RSD socket than pymobiledevice3 tunneld produces.
  • CDS opens a plain nw_connection with http2_transport directly to [fdc5:...]:57346. The first bytes that hit the endpoint are not what the other side expects, and something on the pymobiledevice3-tunneld chain (or the iPhone RSD listener itself) resets the connection.

In other words: this is not "route the pair service somewhere else". This is "CDS and pymobiledevice3 are two independent client implementations of the RSD family, and CDS cannot talk to a channel produced by pymobiledevice3 tunneld".

Implications for Stage 2 plan

Options α and γ as originally framed in plan-stage2-pair-flow.md both assumed CDS could talk natively to the existing pymobiledevice3 tunnel if we just stopped lying about state and let the natural pair path run. That assumption is now falsified.

The viable design is Option δ — CDS → pymobiledevice3 backend shim:

  • Our inject already interposes MDRemoteServiceSupport methods (retrievePropertiesForUUID:, retrieveNameForUUID:) and serves them from in-memory state. This is the same pattern, applied at the transport layer.
  • The key task for Phase C research is to identify the exact call site where CDS decides "open nw_connection to this tunnel-address:tunnel-port", so we can interpose above that point and substitute a connection backed by a local pymobiledevice3 client session (or equivalently, a tiny translating listener that speaks the CDS-expected dialect on one side and the pymobiledevice3 transport on the other).
  • Pair, DDI mount, developer-mode query, DVT calls, app install, debug — all of them use the same underlying RemoteXPC fabric, so a correct shim at the right layer unlocks the entire downstream chain, not just Pair.

Open questions for Phase C (revised)

C.1 — Exact call chain CDS takes to open a service socket when a PairAction invocation runs. Which symbols in CoreDeviceService, CoreDevice.framework, or MobileDevice.framework resolve the (host, port) tuple and then call nw_connection_create? Is the decision made inside CDS, inside a private framework, or inside a helper XPC service?

C.2 — What client-side protocol does pymobiledevice3 implement on top of the raw TCP connection to [fdc5:8480:2949::1]:57346? (RemoteXPC / HTTP2 / QUIC-legacy / TLS1.3 + ALPN / custom framing?) Read pymobiledevice3/remote/* to find the exact sequence.

C.3 — What client-side protocol does Apple-native CDS expect at that endpoint? Disasm around the nw_connection_create call site and look at the http2_transport configuration, the ALPN list, and any TLS context setup.

C.4 — Is there a layer where CDS's call can be intercepted such that we can hand it a socket fd that is already plumbed through pymobiledevice3's Python-side session? Candidates: nw_connection_create itself, xpc_remote_connection_create, any "open service on device" helper in MobileDevice.

C.5 — pymobiledevice3's service implementations for com.apple.mobile.lockdown.remote.* and com.apple.remotepairing — what exactly do they do on the wire to make pair work? Is there an SDK-exposed API we can drive from Objective-C via Python-embed, or is it simpler to run pymobiledevice3 as a sibling daemon and IPC over a Unix socket?

C.6 — (from earlier roadmap) DeviceIdentifier.uuid(UUID, String) second slot — is the empty string we populate correct for pre-pair, or does CDS derive identity from it somewhere downstream?

Questions from the original Phase C that are now closed by this report:

  • C.1 original (iOS 17+ pair service endpoint) — answered: it's the unified RSD endpoint tunnel-address:tunnel-port, not a per-service port.
  • C.2 original (does CDS use remote_service_create_connected_socket?) — answered: no, CDS opens nw_connection directly, bypassing the API we interpose.
  • C.5 original (default unpaired DeviceInfo layout) — answered by smoke test: with our three lies removed, CDS defaults produce the expected state=connected (no DDI) and Xcode shows the Pair button correctly.