Skip to content

Generate Remote AltServer pairing files with idevice - #3

Open
ojura wants to merge 4 commits into
jaakkopalvaila:ngfrom
ojura:pairing-file-request
Open

ojura wants to merge 4 commits into
jaakkopalvaila:ngfrom
ojura:pairing-file-request

Conversation

@ojura

@ojura ojura commented Sep 26, 2026 •

Copy link
Copy Markdown

AltStore Classic 2.3's Set up Remote AltServer… sends AltServer a PairingFileRequest. AltServer-Linux rejected it ("AltServer does not support this request"), so AltStore showed "unable to pair". This PR implements it, following AltServer for macOS.

Depends on jaakkopalvaila/AltServer-Windows#1, which adds the request handler to ClientConnection and the DevicePairingManager interface. The upstream_repo submodule here points at that PR's commit (b0d2846). If that PR is squashed or rebased on merge, this pointer needs updating to the merged commit.

What it does

  • src/DevicePairingManager.cpp is a port of DevicePairingManager.swift:
    • finds the device's usbmuxd device_id by UDID
    • creates a usbmuxd provider
    • pairs with idevice's tunnel_pair_usb(); the device shows a Trust prompt for "AltServer on "
    • serializes the RPPairing file
  • Differences from the Swift version:
    • It reads USBMUXD_SOCKET_ADDRESS once, by libusbmuxd's rules (UNIX:<path>, <host>:<port>, [<host>]:<port>, otherwise /var/run/usbmuxd), and uses that address for both the device lookup and the pairing, so netmuxd setups keep both on the same muxer. idevice's own reader rejects the UNIX: form.
    • It accepts only the device's USB entry. netmuxd in shim mode lists the same UDID once over USB and once over the network, and pairing must not go over the network entry.
    • The usbmuxd connection used for the lookup is closed before pairing starts.
    • It frees the usbmuxd address when creating the provider fails; the Swift version leaks it on that path.
    • A second request for a device that is already waiting on its Trust prompt fails at once with "AltServer is already pairing with this device". tunnel_pair_usb() can't be cancelled, so there is no timeout.
    • Pairing runs on its own thread with an 8 MiB stack. On a cpprestsdk pool thread, polling idevice's pairing future overflowed musl's 128 KiB default thread stack: a SIGSEGV in tunnel_pair_usb, with rsp about 130 KiB below the thread's stack top.
  • libraries/idevice is jkcoxson/idevice v0.1.68, built with usbmuxd,tunnel_tcp_stack,ring.
    • Its static library also defines libplist-compatible plist_* functions, plus the Rust standard library. Both clash with the vendored libplist.
    • So makefiles/idevice-build/idevice.mak prelinks it with ld -r and keeps only the 18 functions in idevice-api.txt global (objcopy --keep-global-symbols).
    • The final binary has one global plist_new_dict, libplist's, and no unresolved symbols.
  • Build:
    • IDEVICE_FFI_LIB points make at a prebuilt library. Without it, the Makefile runs cargo on every make and lets cargo decide whether anything changed. idevice.mak sets .DELETE_ON_ERROR.
    • Cross builds go to libraries/idevice/target/cross, which idevice's .gitignore covers.
    • The builder images have no Rust, and rustup ships musl-hosted compilers only for x86_64 and aarch64. So CI cross-compiles idevice on the runner with cargo-zigbuild (build-idevice-cross.sh, image pinned to 0.23.4), one Rust target per matrix entry.
  • README-NG documents all of this as Fix 4 and updates the build instructions.

Testing

  • AltStore Classic 2.3 on an iPhone 16 Pro running iOS 26.6.2:
    • Remote AltServer setup got the pairing file over USB. The same code, in the Ben-Diehlci port, also paired with netmuxd in shim mode listing the phone over both USB and the network.
    • With Prefer Remote AltServer on and AltServer stopped, AltStore installed an app on the device over LocalDevVPN.
  • A PairingFileRequest for a UDID that isn't connected returns DeviceNotFound with the macOS failure reason and recovery suggestion, for USBMUXD_SOCKET_ADDRESS set to 127.0.0.1:27015 and to UNIX:<socket>.
  • x86_64, i586, armv7 and aarch64 build and link statically from the first commit of this PR, using the builder images plus the cross-built library. The armv7 and aarch64 binaries were started under QEMU user emulation and found the device through usbmuxd.

🤖 Generated with Claude Code

AltStore Classic 2.3's "Set up Remote AltServer" sends PairingFileRequest,
which AltServer-Linux rejected. upstream_repo now handles the request
(PairingFileRequest handler in AltServer-Windows ng-fixes); this commit
adds the Linux implementation and the idevice build.

src/DevicePairingManager.cpp ports DevicePairingManager.swift from AltServer
for macOS: it finds the device's usbmuxd device_id by UDID, builds a
usbmuxd provider, pairs with tunnel_pair_usb() (the device shows a Trust
prompt) and serializes the RPPairing file. Both usbmuxd connections honor
USBMUXD_SOCKET_ADDRESS, like the rest of AltServer-Linux. The pairing runs
on a thread with an 8 MiB stack because polling idevice's pairing future
overflowed the 128 KiB default stack of musl threads (SIGSEGV in
tunnel_pair_usb on a cpprestsdk pool thread).

libraries/idevice is jkcoxson/idevice v0.1.68 with the features
usbmuxd,tunnel_tcp_stack,ring. Its static library also defines
libplist-compatible plist_* functions and the Rust standard library, which
clash with the vendored libplist, so makefiles/idevice-build/idevice.mak
prelinks it with ld -r and keeps only the functions in idevice-api.txt
global. IDEVICE_FFI_LIB selects a prebuilt library; without it the
Makefile runs cargo. The builder images have no Rust toolchain, so CI
cross-compiles the library for each architecture with cargo-zigbuild
(build-idevice-cross.sh) and passes it in.

Tested with AltStore Classic 2.3 on an iPhone 16 Pro running iOS 26.6.2:
Remote AltServer setup received the pairing file, and with Prefer Remote
AltServer on and AltServer stopped, AltStore installed an app on-device.
x86_64, i586, armv7 and aarch64 build and link statically; the armv7 and
aarch64 binaries were started under QEMU user emulation and found the
device through usbmuxd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ojura and others added 3 commits September 26, 2026 21:16
DevicePairingManager left USBMUXD_SOCKET_ADDRESS to idevice, which
reads every value containing ':' as a numeric TCP address. So the
libusbmuxd form UNIX:<path>, the one deploy/altserver-stack.yml sets,
failed with FfiInvalidArg and AltStore got a generic "couldn't be
configured" error, although the rest of AltServer accepts that value.
The address is now read once by libusbmuxd's rules (UNIX:<path>,
<host>:<port> or [<host>]:<port>, otherwise /var/run/usbmuxd), and the
device lookup and the pairing provider both connect to it.

usbmuxd can list one UDID twice, over USB and over the network. The
lookup took the first match, so a network entry could be paired over.
It now accepts only an entry whose connection type is USB, which is
what the DeviceNotFound message already says.

The usbmuxd connection used for the device list is closed as soon as
the device_id is known, instead of staying open until the user answers
the Trust prompt.

idevice-api.txt exports the three functions this needs and drops the
two it no longer calls.

Tested with a temporary instance and a PairingFileRequest for a UDID
that is not connected: DeviceNotFound with UNIX:<socket> relayed to
netmuxd and with 127.0.0.1:27015. The build before this change
answered the UNIX: case with "idevice error 33: FfiInvalidArg". The
phone's UDID, listed by netmuxd only as Network, also gets
DeviceNotFound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the way

The cargo rule had no prerequisites, so after the first build make
never ran cargo again: a submodule bump or a change to
idevice-features.txt was prelinked from the old library. cargo now runs
on every make. When nothing changed it leaves libidevice_ffi.a
untouched, so make does not prelink again.

idevice.mak sets .DELETE_ON_ERROR like the other makefiles, so a failed
ld or objcopy does not leave a partial idevice_ffi.o that the next make
treats as built.

build-idevice-cross.sh builds into libraries/idevice/target/cross
instead of target-cross. idevice's .gitignore covers target/, so the
submodule no longer shows untracked content after a cross build.

Tested in rust:latest with plain make: an unchanged tree and a touched
idevice-features.txt run cargo in about 0.1 s and leave idevice_ffi.o
alone; adding a feature rebuilds the crate and prelinks again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tunnel_pair_usb() returns only when the user answers the Trust prompt,
the device disconnects or the connection fails, and idevice has no call
that cancels it. So each retry while the prompt was still up started
another pairing with the same device and blocked one more request
thread and 8 MiB pairing stack.

A request for a UDID that is already pairing now fails at once with
"AltServer is already pairing with this device." The first pairing
still waits for the user, as the macOS version does.

Tested with a fake usbmuxd that lists the UDID as Network and then USB
and never answers the pairing's Connect: the pairing connects with the
USB entry's DeviceID, a second request fails in 0.03 s, and after the
fake drops the connection a new request is accepted. Without this
change the second request got no reply within 60 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jaakkopalvaila

Copy link
Copy Markdown
Owner

Thanks for this, and sorry for the slow reply. The pairing support is useful, and the write-up made it easy to follow.

The ng branch moved a lot in October, so the PR now has conflicts:

Could you rebase this onto the current ng, with upstream_repo pointing at your #1 rebased onto ng-fixes? Then I can merge both, and I will test the Remote AltServer setup with AltStore 2.3 on an iPhone on iOS 27.

If you don't have time for that, just tell me, and I will do the rebase myself and keep you as the author.

@ojura

ojura commented Oct 27, 2026

Copy link
Copy Markdown
Author

hey @jaakkopalvaila, thanks for having a look. yeah, under a bit of a crunch lately. if you have the time to rebase it, would be grateful. (based from my experience, Claude can probably transfer all useful work on this branch to the new base with little intervention. Then it was mostly running rounds of reviews until it saturated.)

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.

2 participants