Skip to content

iOS: full-tunnel VPN app — UI parity, build fix, SNI pool#1398

Merged
therealaleph merged 9 commits into
therealaleph:mainfrom
yyoyoian-pixel:ios-full-tunnel-app
May 28, 2026
Merged

iOS: full-tunnel VPN app — UI parity, build fix, SNI pool#1398
therealaleph merged 9 commits into
therealaleph:mainfrom
yyoyoian-pixel:ios-full-tunnel-app

Conversation

@yyoyoian-pixel
Copy link
Copy Markdown
Contributor

Summary

iOS NetworkExtension full-tunnel VPN client (leaf + FakeIP → SOCKS5 → mhrv-rs).

  • Build fix: produce the static lib via cargo rustc --crate-type staticlib. cargo build --lib also links the cdylib, which fails on iOS (undefined ___chkstk_darwin under -nodefaultlibs), aborting the build and leaving a stale .a so device builds never picked up changes.
  • utun fd: getsockopt(UTUN_OPT_IFNAME) fallback when the KVC keypath returns nil on newer iOS; dup() so leaf owns the fd.
  • leaf logging: level none so leaf's tracing init doesn't panic against the already-installed global subscriber; panic hook logs payload + location.
  • UI: full-tunnel mode, multiple deployment IDs, auth key show/hide, SNI pool editor with per-SNI TLS probe, auto-detect Google IP, QUIC/STUN/DoH toggles.
  • Config import: mhrv-rs:// (zlib) + JSON/TOML, Android-compatible.
  • Signing: team read from a gitignored Local.xcconfig (no team ID in VCS); generated .xcodeproj and build/ gitignored.
  • App icon; version from MARKETING_VERSION; iOS README incl. TestFlight steps.

Other modes (apps_script/direct) and the SNI native probe are deferred to follow-up PRs.

Test plan

  • cd ios && cp Local.xcconfig.example Local.xcconfig (set team), xcodegen generate, build to device
  • Connect VPN; verify traffic (browsing, Telegram)
  • SNI pool toggles + "Test all" probe
  • Config import/share round-trips with Android

yyoyoian-pixel and others added 9 commits May 25, 2026 23:02
iOS NetworkExtension full-tunnel client (leaf + FakeIP -> SOCKS5 -> mhrv-rs):

- Build: produce the static lib via `cargo rustc --crate-type staticlib`.
  `cargo build --lib` also links the cdylib, which fails on iOS
  (undefined ___chkstk_darwin under -nodefaultlibs), aborting the build
  and leaving a stale .a so device builds never picked up changes.
- utun fd: getsockopt(UTUN_OPT_IFNAME) fallback when the KVC keypath
  returns nil (newer iOS), plus dup() so leaf owns the fd independently.
- leaf log level "none" so leaf's tracing init doesn't panic against the
  already-installed global subscriber; panic hook logs payload + location.
- UI: full-tunnel mode, multi deployment IDs, auth key show/hide, SNI pool
  editor with per-SNI TLS probe, auto-detect Google IP, QUIC/STUN/DoH toggles.
- Config import: mhrv-rs:// (zlib) + JSON/TOML, Android-compatible.
- Signing team read from a gitignored Local.xcconfig (no team ID in VCS);
  generated .xcodeproj and build/ are gitignored.
- App icon; version from MARKETING_VERSION; iOS README incl. TestFlight steps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ITSAppUsesNonExemptEncryption=false skips the export-compliance prompt on
TestFlight uploads (standard TLS only). Also removes a duplicate
CFBundleIdentifier key from Info.plist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Explains appending a unique suffix (e.g. .pixel) to the app + extension
bundle IDs (and matching tunnelId) to install a parallel build without
claiming the reserved com.therealaleph.mhrv ID. Local-only; not committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App Store validation 90474: a universal (iPhone+iPad) build must support all
four interface orientations for iPad multitasking, but the app is portrait-only.
Targeting iPhone only drops that requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds coalesce_step_ms / coalesce_max_ms controls (full-mode tunnel batching)
to the Advanced section, matching the Android settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ForEach(enumerated, id: \.offset) with index-based bindings would read a
stale/out-of-range index after a row was removed. Guard all index accesses
in the get/set and the delete button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tapping empty space or scrolling now resigns first responder. Interactive
controls consume their own taps, so fields and buttons keep working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Share now emits mhrv-rs:// + URL-safe base64 of zlib-compressed (RFC 1950)
JSON — byte-compatible with Android's ConfigStore.encode/decode. Import also
falls back to raw UTF-8 for uncompressed payloads. Verified round-trip against
Android's zlib decode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@therealaleph
Copy link
Copy Markdown
Owner

Validated locally before merge:

  • cargo test --lib — 248 passed, 0 failed.
  • bash -n scripts/build-ios.sh — passed.
  • plutil -lint ios/App/Info.plist ios/NetworkExtension/Info.plist — passed.
  • ruby -e 'require "yaml"; YAML.load_file("ios/project.yml")' — passed.
  • git diff --check — passed.

I also attempted cargo check --target aarch64-apple-ios --lib --profile release-ios; this local Mac is currently using CommandLineTools rather than full Xcode, so the check stops at xcrun: SDK "iphoneos" cannot be located. That is a local SDK availability blocker, not a code failure from the PR.

Merging this now.


Answered via LLM, Supervised @therealaleph

@therealaleph therealaleph merged commit 0eaddbc into therealaleph:main May 28, 2026
1 check passed
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