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=.connectedkept (and re-added after initial Phase A revert caused Xcode to filter the device out) — it is a truthful link-state value because anRSDDeviceWrapperwith a live tunnel exists.- Restored env via
iosmux-restore.sh: host NCM bridge up, havoc-sidepymobiledevice3 remote tunneldrunning on127.0.0.1:49151, tunnel endpoint[fdc5:8480:2949::1]:57346advertised. - Fresh inject deployed to
/Library/Developer/CoreDevice/, CDS restarted. Xcode Devices window opened: device row visible with Pair button,Copy Identifierreturns<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.log—log stream predicate='process == "CoreDeviceService"'/tmp/s2b_xcode.log—log stream predicate='process == "Xcode"'/tmp/s2b_remotepairing.log—log 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:
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 /
remotedstack, which speaks a different dialect on top of the RSD socket than pymobiledevice3 tunneld produces. - CDS opens a plain
nw_connectionwithhttp2_transportdirectly 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
MDRemoteServiceSupportmethods (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 opensnw_connectiondirectly, 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.