Skip to content

About

Prebuilt iOS, tvOS, macOS, and Android SDK packages for xray-rust with SwiftPM, XCFramework, AAR, and native tunnel adapters.

Topics

Resources

Contributing

Security policy

Stars

10 stars

Watchers

0 watching

Forks

Repository files navigation

xray-rust-mobile

Maven Central

Project website: xray-rust.aimalygin.chatgpt.site

SDK 0.8.1 includes the Apple startup-probe ordering fix, updated core dependencies and C ABI 1.12. Trojan, Shadowsocks 2022, VMess AEAD, Hysteria2 and WireGuard are supported; Shadowsocks AEAD-2017 and legacy VMess remain excluded. See GitHub Releases for downloadable artifacts and publication status, and the changelog for the release scope. The core 0.8.1 evidence and limitations retain the conditional SS2022 WAN PMTU result, remote EOF limitation and unconfirmed 5% latency non-regression bound accepted for this release.

The development branch tracks Unreleased changes against core 6a84f88, which includes HTTP/HTTPS and SOCKS5 link import (C ABI 1.14), the Xray-core v26.10.21 migration and the opt-in Android network-change coordinator from core #97. See the core migration record for verified scope and limits. This source synchronization leaves the last released version fields at 0.8.1; local builds of this checkout are development snapshots, not the published 0.8.1 artifacts. The commit pin and unprepared artifact locks block publication. Use the installation examples below for published 0.8.1 packages. For source development, build the native artifacts locally before resolving the Swift package; see Unreleased changes. A future release version and acceptance scope will be chosen separately.

Native iOS, tvOS, macOS, and Android SDK packages for xray-rust. The repository provides ready-to-integrate binaries, Swift and Kotlin APIs, and native tunnel adapters for host applications.

Each stable release pins one reviewed core commit and publishes:

  • XrayRust.xcframework.zip for Swift Package Manager;
  • io.github.aimalygin:xray-rust-mobile as an Android AAR/Maven module;
  • matching checksums and a release manifest.

Release candidates use MAJOR.MINOR.PATCH-rc.N tags and are GitHub prereleases. They contain the verified XCFramework, standalone AAR, the raw project LICENSE, third-party notices, checksums, and manifest, but are never published to GitHub Packages or Maven Central.

An Apple Packet Tunnel extension can use the provided implementation directly:

import XrayAppleTunnel

final class PacketTunnelProvider: XrayPacketTunnelProvider {}

The underlying core measured 4.2 MiB idle resident memory and 20.9 MiB with 1,000 held SOCKS flows in the latest published synthetic localhost benchmark. See the full results and methodology.

This project is unofficial and is not affiliated with XTLS or Xray-core.

Development HTTP proxy import

Build this source snapshot with its pinned core, then check XrayCore.ffiInfo.supportsProfileImport(.http) in Swift or XrayCore.ffiInfo().supportsProfileImport(XrayProfileFormat.Http) in Kotlin. Pass either http:// or https:// to the matching profile importer. HTTPS validates the proxy certificate and name; explicit resolver IPs use TCP DNS. See the accepted URI subset and examples. Published 0.8.1 binaries do not provide this new capability.

Version mapping

Mobile SDK xray-rust Core commit C ABI
Unreleased source snapshot commit pin; core reports 0.8.1 6a84f8833c7cad09ea3cd4eaa324f022f73851e5 1.14
0.8.1 v0.8.1 5b61eee201fac8639cb1dac0349e34af65db90f7 1.12
0.8.0 v0.8.0 fcce4be2771c1fb3f11e9503741d39a4d8df314e 1.11
0.7.0 v0.7.0 67969094b352f948c6b8b9e2ac75402c577cb7f7 1.7
0.6.1 v0.6.1-rc.1 1231add417a4e2e6aa4c9e51c13487d493498a1d 1
0.6.0 v0.6.0 8fd695d3da7ae98051b55b598aa7a16c8ecb1db5 1
0.6.0-rc.1 (candidate) v0.6.0-rc.1 1e713ca3e6c57be5747b4915b31e0db040a01c0c 1
0.5.0 v0.5.0 549807d621fadc618e6d0bab75f9e58ef35a7fc1 1
0.4.0 v0.4.0 e1199b2176ae834259e8a2b21db468bb9db5fb17 1
0.3.2 v0.3.2 850813037cd5c018348ec08b44b0b926414e17e8 1
0.3.1 v0.3.1 9dba6c222ce24d347fc97fbedfedadaeb16a512c 1
0.3.0 v0.3.0 774e08d0a22a2ff30a2ade38b9d616efe43661e7 1
0.2.0 v0.2.0 536d2640fe0fc2df87b61b128b21d3886d72951d 1
0.1.7 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.6 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.5 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.4 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.3 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.2 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.1 v0.1.1 ae14066eedca532e247503a19481263a437011c4 1
0.1.0 v0.1.0 e4daf171c6c44730312d4e35294b25e60691291f 1

For releases that change the core or its adapters, the Mobile SDK version stays aligned with the pinned xray-rust tag. A packaging-only Swift or Kotlin respin may advance independently without inventing a new Rust core version. The complete trust anchor is in release/core.env.

Development synchronization after 0.8.1

The development snapshot includes Android's unmetered VPN setting, cached FFI metadata, checked native statistics and wrapper cleanup from core #82/#88. SOCKS inbound authenticates the TCP control connection; password-protected UDP associations require a declared nonzero client port and enforce that source endpoint. UDP datagrams carry no credentials. Association cleanup is also included in core. SOCKS outbound supports TCP CONNECT and UDP ASSOCIATE over raw TCP or TLS control connections; relay UDP datagrams remain plain. Its server domain is bootstrapped before tunnel startup and Apple excludes the resolved carrier IPs from tunnel routes. Destination-domain resolution remains with the SOCKS proxy. HTTP outbound TCP CONNECT is available through canonical JSON. SOCKS share-link import accepts the bounded subset below; UDP proxy chaining remains unsupported. See the pinned compatibility policy for supported behavior and deliberate differences from Xray-core.

Android apps can opt into XrayNetworkChangeCoordinator on their serial VPN control executor. Supply ACCESS_NETWORK_STATE and a policy that selects a usable non-VPN network, applies actual carrier/socket-binding settings, then returns that network (or null while offline). The helper waits for callback metadata, coalesces events, and requests Hysteria2/WireGuard rebind independently. Initial observation and loss without a replacement do not request migration. Call close() before core shutdown and borrowed-fd close; queued events from an old session cannot reach the next core.

Results count accepted/coalesced requests, not recovered traffic; idle outbounds return zero without opening connections. Protection and existing ABI guards remain in effect. For ordinary default routing retain setUnderlyingNetworks(null); an explicit list must describe actual Network.bindSocket use and does not move sockets. DNS/NAT64 refresh and other protocols need host restart policy. See the compiled fd-session example and full lifecycle contract. This helper changes no C ABI, defaults, fd ownership or coroutine dependencies. Physical Wi-Fi↔cellular validation with active TCP/UDP remains open in core #89; localhost JNI continuity checks do not establish Android migration support. The SDK's packaging toolchain remains independently pinned. Development CI checks source/package integration, not new physical-device or performance acceptance.

New APIs in 0.8.1

The Apple provider performs cancellable startup-probe preflight after DNS bootstrap and before installing tunnel network settings. With a startup probe enabled, the native library must expose ABI 1.12's STARTUP_PROBE_PREFLIGHT capability. Probe failure, timeout or cancellation leaves network settings untouched. The provider disables the already-completed probe on its data-path core. Custom integrations can use XrayCore.preflightStartupProbe() on a loaded, unstarted core; ordinary start() retains its existing startup-probe behavior.

Both adapters expose Hysteria2/WireGuard rebind, outbound URL probes, XrayHysteriaStreamLimits and XrayTunAdmissionOptions through XrayCore. Supply limits and admission when creating the core, before loading its config. The default Hysteria2 limits remain 64 TCP streams and 32 UDP sessions per QUIC session; explicit limits accept 1–256 and 1–128 respectively. Rebind returns the number of accepted/coalesced requests, not a network-change completion signal. The Android host owns its network-change observer.

TUN admission is opt-in and runs once per flow on bounded native workers, off the packet loop. Callbacks must return promptly and must not call core lifecycle methods. Timeout (1–5000 ms) bounds the decision, not callback execution. Keep policy state safe for concurrent callbacks. With admission disabled there are no per-packet host callbacks. Kotlin's XrayAndroidUidAdmission requires Android 10+ and an active VPN; unknown owners and lookup errors deny by default. Keep VpnService.Builder app rules as well. Override XrayVpnService.tunAdmissionOptions() to register this policy after VPN establishment. Some devices report an unknown UID even for permitted, explicitly bound TCP sockets; test that case before opting into strict UID enforcement.

The supplied XrayPacketTunnelProvider exposes both options through overridable hysteriaStreamLimits() and tunAdmissionOptions() methods:

import XrayAppleTunnel
import XrayMobileAdapter

final class PacketTunnelProvider: XrayPacketTunnelProvider {
    override func hysteriaStreamLimits() throws -> XrayHysteriaStreamLimits? {
        try .init(maxTcpStreams: 128, maxUdpSessions: 64)
    }

    override func tunAdmissionOptions() throws -> XrayTunAdmissionOptions? {
        try .init(timeoutMilliseconds: 100, failOpen: false) { flow in
            // Example policy: allow only IPv4 destinations. Replace with host policy.
            flow.addressFamily == 4
        }
    }
}

These hooks run once per configured start, before DNS bootstrap and network settings; a thrown error fails startup. Their settings remain fixed until restart and apply to both direct utun fd and packetFlow backends. Default implementations return nil, preserving the core's 64/32 limits and disabled admission path. Admission callbacks may run concurrently and finish after tunnel shutdown: avoid retaining the provider or calling lifecycle methods from a callback. Explicit denial remains denied with failOpen: true; queue/buffer exhaustion also drops. These are typed host policies, not additional Xray JSON/provider dictionary keys. Apple does not provide the Android UID lookup helper.

Salamander, UDP port hopping, first-ClientHello fragmentation and UDP noise use the pinned core's JSON configuration. Hysteria2 profile import also preserves obfs=salamander, obfs-password and mport. Fragmentation supports the core's bounded TCP TLS/REALITY/Vision configurations; it does not apply to QUIC or arbitrary UDP. UDP noise uses bounded finalmask.udp settings on Hysteria2 and XHTTP with exact alpn: ["h3"], including mask ordering with Salamander. Legacy freedom settings.noises is unsupported. Linux TPROXY is a desktop/server inbound; Apple/Android applications continue using the TUN adapters. These features stay disabled unless configured. Probe calls are blocking; call them off the main thread. Capability/version checks guard the optional host APIs; distribute the matched native library and adapters together.

The pin includes core compatibility and performance evidence, including pinned Xray-core and native Hysteria interoperability. SDK sync keeps the canonical adapters byte-for-byte and retains release build profiles and dependency locks. Host measurements are not a new physical-device acceptance campaign; the report retains unexplained UDP timeouts and existing release gates. No artifacts are published by updating this development pin.

Apple platforms with Swift Package Manager

Add the package:

dependencies: [
    .package(
        url: "https://github.com/aimalygin/xray-rust-mobile.git",
        exact: "0.8.1"
    ),
]

Then add the product needed by the target:

.product(
    name: "XrayMobileAdapter",
    package: "xray-rust-mobile"
)

Available products:

  • XrayMobileAdapter — lifecycle, packet I/O, direct utun fd support, statistics, diagnostic events, and the NEPacketTunnelFlow pump;
  • XrayAppleShared — profiles, Keychain-backed config storage, VLESS URL import (including XHTTP over plaintext, TLS, and REALITY), logging, and DNS preflight;
  • XrayAppleTunnel — a ready-to-subclass NEPacketTunnelProvider.

Minimum supported versions are iOS 15, tvOS 17, and macOS 11. The binary contains arm64 device slices and arm64 plus x86_64 simulator slices for iOS and tvOS, plus a universal arm64/x86_64 macOS slice. XrayAppleTunnel APIs that depend on newer Network Extension functionality require macOS 13.

XrayRust.xcframework deliberately ships bare libxray_ffi.a slices and no headers. Xcode copies the headers of a static-library XCFramework into a flat per-configuration include directory, and the module.modulemap name there is fixed, so an app that also links another static-library XCFramework — a second Xray core, for instance — fails to build with "Multiple commands produce .../include/module.modulemap". The public C API is published from the XrayRustFFI target of this package instead, which vendors xray_ffi.h and its module map verbatim from the pinned core. import XrayRust is unaffected. The practical consequence is that the XCFramework must be consumed through Swift Package Manager: dropping it straight into an Xcode project provides the library but no declared module, so copy the two files from Sources/XrayRustFFI/include alongside it.

Low-level lifecycle:

import XrayMobileAdapter

let core = try XrayCore(configJSON: configJSON)
try core.start()
defer { try? core.stop() }

For a packet-tunnel extension, the higher-level entry point is:

import XrayAppleTunnel

final class PacketTunnelProvider: XrayPacketTunnelProvider {}

Store the JSON in Keychain and put only its opaque reference in the provider configuration:

import XrayAppleShared

let reference = XraySecureConfigReference.tunnel(profileID)
try XrayKeychainConfigStore().store(
    configJSON: configJSON,
    reference: reference
)
providerConfiguration[
    XrayTunnelProviderMessage.providerConfigReferenceKey
] = reference

XrayKeychainConfigStore writes to the target's default Keychain access group. In both the host app and packet-tunnel extension, enable Keychain Sharing and make the same shared group the first entry in keychain-access-groups:

<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)com.example.xray.shared</string>
</array>

The order matters: when no explicit access group is supplied, Apple uses the first Keychain access group as the default. The host app still owns signing, Network Extension entitlements, the provider bundle identifier, and VPN consent. Never place the raw configuration JSON in providerConfiguration or start options.

Shared App Group geodata

Hosts that download or share geosite.dat and geoip.dat should give the containing app and Packet Tunnel extension the same App Group entitlement:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.example.xray</string>
</array>

Publish each checksum-verified generation into its own directory, for example:

Library/Application Support/XrayGeodata/<version>-<sha256>/
  geosite.dat
  geoip.dat

Select that exact generation in the provider configuration:

providerConfiguration[
    XrayTunnelProviderMessage.providerGeodataAppGroupIdentifierKey
] = "group.com.example.xray"
providerConfiguration[
    XrayTunnelProviderMessage.providerGeodataRelativeDirectoryKey
] = "Library/Application Support/XrayGeodata/<version>-<sha256>"

Both values are required together. The relative directory must already exist inside the App Group and every component must be a real directory rather than a symbolic link. Empty, . and .. path components are rejected. With both keys absent, the Packet Tunnel keeps the existing Bundle.main.resourceURL fallback. With both keys present, the selected generation is exclusive: a referenced asset missing from it fails preflight instead of being loaded from the process-default search directories.

Download into a sibling staging directory, verify checksums, then atomically rename the completed directory to its final generation name. Published generations are immutable: do not replace their files or use a mutable current symlink, and retain the selected generation until the tunnel has stopped and no saved provider configuration refers to it.

The App Group is a trusted cooperation boundary. The adapter validates the selected path when the tunnel starts, but it does not hold directory file descriptors or defend against another App Group writer replacing a published generation concurrently. It does not download or verify geodata on the host's behalf.

Android with Gradle

Stable Android SDK versions are published to Maven Central and do not require GitHub credentials. RC builds are available only as standalone AAR assets on their GitHub prereleases.

In settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

In the app module's build.gradle.kts:

dependencies {
    implementation("io.github.aimalygin:xray-rust-mobile:0.8.1")
}

The published coordinate, POM, signatures, sources, and API documentation are listed on Maven Central.

Hosts that expose an explicit debug-logging preference can enable the core's sanitized access and error logs by creating a private directory before loading the configuration:

val logDirectory = File(context.filesDir, "xray-logs").apply { mkdirs() }
val core = XrayCore.create(
    configJson = configJson,
    vpnService = vpnService,
    fileLoggingDirectory = logDirectory,
)

Omit fileLoggingDirectory during normal operation. The core never enables file logging by itself, and the host remains responsible for bounding and exporting the generated xray-access.log and xray-error.log files.

Every GitHub release also contains the standalone AAR for consumers that need to inspect or integrate the release artifact directly.

The AAR supports API 24+ and contains arm64-v8a, armeabi-v7a, x86, and x86_64, with 16 KiB page-aligned native libraries. It merges the INTERNET and FOREGROUND_SERVICE permissions. The library deliberately does not register its base XrayVpnService or choose a target-SDK-specific service type: the host must subclass it, register that concrete service, and declare the matching permission.

class AppVpnService : XrayVpnService() {
    fun connect(configJson: String) {
        startXrayTunnel(
            configJson = configJson,
            tunRuntimeProfile = XrayTunRuntimeProfile.Mobile,
        )
    }

    override fun onXrayTunnelStartFailed(error: Throwable) {
        // Report a sanitized failure to the host app.
    }
}

Register the subclass in the host app manifest. Android 14+ requires both the foreground-service type and its matching permission for an active VPN:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission
        android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />

    <application>
        <service
            android:name=".AppVpnService"
            android:exported="false"
            android:foregroundServiceType="systemExempted"
            android:permission="android.permission.BIND_VPN_SERVICE">
            <intent-filter>
                <action android:name="android.net.VpnService" />
            </intent-filter>
        </service>
    </application>
</manifest>

The host app remains responsible for VpnService.prepare, a foreground notification/channel, target-SDK-specific foreground service policy, lifecycle commands, and user-visible error handling. Call startForeground promptly; Android permits systemExempted only while the app is configured as an active VPN, otherwise startup can fail with ForegroundServiceTypeNotAllowedException.

Development SOCKS5 profile import

The unreleased source snapshot adds Swift .socks and Kotlin XrayProfileFormat.Socks to XrayProfileImporter.profile. Both socks:// and socks5:// select SOCKS5. Check supportsProfileImport before offering this format: ABI 1.13, PROFILE_IMPORT and SOCKS_PROFILE_IMPORT are required. Published 0.8.1 binaries do not expose this importer.

Links require a host and explicit port and accept optional percent-encoded or Base64 UTF-8 credentials, bracketed IPv6 and a display-name fragment. Empty Base64 Og== means noauth; incomplete credentials fail. Unknown query options, TLS/transport parameters and legacy whole-authority Base64 fail explicitly. Import is offline and preserves proxy hostnames for the ordinary mobile DNS bootstrap path; destination names are resolved by the SOCKS proxy.

Use the host's secure storage for the returned config and the existing VPN startup path. See the exact source contract for bounds, supported forms, errors and plaintext SOCKS transport behavior.

v0.8 candidate profile imports

The candidate adds XrayProfileFormat.trojan, .shadowsocks2022 and .vmess in Swift, and Trojan, Shadowsocks2022 and Vmess in Kotlin. Call XrayProfileImporter.profile with the selected format and store the resulting config in the host's secure profile store. Check supportsProfileImport on the linked core's FFI information before offering a format to the user.

Trojan accepts trojan://; Shadowsocks 2022 accepts SIP002 ss:// with the three 2022-blake3-* methods; VMess accepts AEAD profiles with zero/absent alterId. Unknown or excluded methods fail import. The importer works offline; loading a profile does not start a VPN. See the core's compatibility contract for transport combinations, resource limits and SS2022 reconnect behavior.

Build locally

The scripts prefer a clean sibling checkout at ../xray-rust; otherwise they clone the exact commit from release/core.env into .build/core.

Apple requires Xcode 16.4. scripts/build-apple.sh rejects another selected Xcode for release reproducibility. To allow a different Xcode for a local non-release build, set XRAY_ALLOW_UNPINNED_XCODE=1.

Apple:

source release/toolchains.env
rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal \
  --component llvm-tools
rustup toolchain install "$TVOS_RUST_TOOLCHAIN" --profile minimal \
  --component rust-src
rustup target add --toolchain "$RUST_TOOLCHAIN" \
  aarch64-apple-darwin \
  x86_64-apple-darwin \
  aarch64-apple-ios \
  aarch64-apple-ios-sim \
  x86_64-apple-ios
scripts/build-apple.sh
scripts/check-apple-link.sh
scripts/package-apple.sh

Android requires JDK 17, Android SDK 35, NDK 26.3.11579264, and CMake 3.22.1:

source release/toolchains.env
rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal
rustup target add --toolchain "$RUST_TOOLCHAIN" \
  aarch64-linux-android \
  armv7-linux-androideabi \
  i686-linux-android \
  x86_64-linux-android
scripts/build-android.sh
scripts/package-android.sh

For an RC preflight, scripts/package-android.sh --standalone-only keeps the locally staged Maven repository available for consumer smoke tests while omitting the Maven archive from the distribution.

Generated native artifacts live under ignored Artifacts/, .build/, and dist/ directories. They are never committed to Git.

Updating the pinned core

  1. Review and update release/core.env, including the tag object, commit, tree, Cargo.lock, header, and module-map hashes.
  2. Run scripts/sync-upstream.sh --force after reviewing that it will replace the local adapter snapshots.
  3. Update release/version.env, Package.swift, Android VERSION_NAME, and CHANGELOG.md.
  4. Run scripts/check-release.sh --prepare until the canonical Apple artifact is locked, then run strict scripts/check-release.sh.
  5. Build both platforms and complete the two-phase release described in docs/releasing.md.

Do not make xray-ffi a Cargo dependency of another workspace. It must be built from the pinned xray-rust workspace root so its locked path dependencies and root [patch.crates-io] remain effective.

License

Source in this repository is licensed under the Mozilla Public License 2.0. Binary releases retain the exact core source commit and third-party notices needed to identify corresponding source; see THIRD_PARTY_NOTICES.md.

About

Prebuilt iOS, tvOS, macOS, and Android SDK packages for xray-rust with SwiftPM, XCFramework, AAR, and native tunnel adapters.

Topics

Resources

Contributing

Security policy

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages