53ae87aa9d
- Set default-features = false on arti-client and tor-rtcompat - Removed bridge-client (UI doesn't expose bridge config yet) - Narrowed tokio features from "full" to only what the SOCKS proxy needs: rt-multi-thread, net, io-util, time, macros Kept: tokio, rustls, compression, onion-service-client, static-sqlite (all required for Amethyst's .onion relay support) https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
210 lines
6.3 KiB
Markdown
210 lines
6.3 KiB
Markdown
# Arti Android Build Tools
|
|
|
|
Custom-built [Arti](https://gitlab.torproject.org/tpo/core/arti) (Tor in Rust) native libraries
|
|
for Amethyst Android. This replaces the Guardian Project's `arti-mobile-ex` AAR with a minimal
|
|
JNI wrapper built directly from Arti source.
|
|
|
|
## Why custom build?
|
|
|
|
| | Guardian Project AAR | Custom build |
|
|
|---|---|---|
|
|
| **Size** | ~140MB | ~11MB |
|
|
| **16KB pages** | No | Yes (NDK 25+) |
|
|
| **Stop/restart** | Broken (state file lock) | Works (TorClient persists, only SOCKS proxy stops) |
|
|
| **Version** | Behind | Pinned to latest (currently 1.9.0) |
|
|
|
|
## Quick start
|
|
|
|
Pre-built `.so` files should be committed to `amethyst/src/main/jniLibs/`. You only need to
|
|
rebuild if you want to verify binaries, update the Arti version, or modify the JNI wrapper.
|
|
|
|
## Prerequisites
|
|
|
|
1. **Rust toolchain**
|
|
```bash
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
```
|
|
|
|
2. **Android targets**
|
|
```bash
|
|
rustup target add aarch64-linux-android x86_64-linux-android
|
|
```
|
|
|
|
3. **cargo-ndk**
|
|
```bash
|
|
cargo install cargo-ndk
|
|
```
|
|
|
|
4. **Android NDK 25+** (required for 16KB page size support)
|
|
```bash
|
|
# Via Android Studio: SDK Manager → SDK Tools → NDK (Side by side)
|
|
# Or via command line:
|
|
sdkmanager "ndk;27.0.12077973"
|
|
|
|
# Set environment variable
|
|
export ANDROID_NDK_HOME="$HOME/Android/Sdk/ndk/27.0.12077973"
|
|
```
|
|
|
|
## Building
|
|
|
|
```bash
|
|
cd tools/arti-build
|
|
|
|
# Build for all targets (arm64 + x86_64)
|
|
./build-arti.sh
|
|
|
|
# Build arm64 only (for release APKs)
|
|
./build-arti.sh --release
|
|
|
|
# Clean rebuild from scratch
|
|
./build-arti.sh --clean
|
|
```
|
|
|
|
The script will:
|
|
1. Clone official Arti source from `gitlab.torproject.org`
|
|
2. Check out the version pinned in `ARTI_VERSION`
|
|
3. Copy the JNI wrapper into the source tree
|
|
4. Compile with `cargo-ndk` for each target architecture
|
|
5. Output `.so` files to `amethyst/src/main/jniLibs/{arm64-v8a,x86_64}/`
|
|
6. Verify JNI symbols are exported correctly
|
|
|
|
## Output
|
|
|
|
```
|
|
amethyst/src/main/jniLibs/
|
|
├── arm64-v8a/
|
|
│ └── libarti_android.so (~5-6 MB)
|
|
└── x86_64/
|
|
└── libarti_android.so (~6-7 MB, emulator support)
|
|
```
|
|
|
|
## Verifying 16KB page alignment
|
|
|
|
Google Play requires 16KB page-aligned native libraries. Verify with:
|
|
|
|
```bash
|
|
readelf -l amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so | grep LOAD
|
|
```
|
|
|
|
The first LOAD segment alignment should be `0x4000` (16384 bytes).
|
|
|
|
## Directory structure
|
|
|
|
```
|
|
tools/arti-build/
|
|
├── README.md # This file
|
|
├── ARTI_VERSION # Pinned Arti git tag (e.g., arti-v1.9.0)
|
|
├── Cargo.toml # Rust dependencies and build profile
|
|
├── build-arti.sh # Build script
|
|
├── src/
|
|
│ └── lib.rs # JNI bridge (Rust → Kotlin)
|
|
└── .arti-source/ # [gitignored] Cloned Arti repository
|
|
```
|
|
|
|
## Updating Arti version
|
|
|
|
1. Check available versions:
|
|
```bash
|
|
git ls-remote --tags https://gitlab.torproject.org/tpo/core/arti.git | grep 'arti-v' | tail -10
|
|
```
|
|
|
|
2. Update the version file:
|
|
```bash
|
|
echo "arti-v1.10.0" > ARTI_VERSION
|
|
```
|
|
|
|
3. Update crate versions in `Cargo.toml` to match the new release.
|
|
Check the crate versions at:
|
|
```
|
|
https://gitlab.torproject.org/tpo/core/arti/-/raw/arti-v1.10.0/crates/arti-client/Cargo.toml
|
|
```
|
|
|
|
4. Rebuild and test:
|
|
```bash
|
|
./build-arti.sh --clean
|
|
```
|
|
|
|
## Architecture: JNI bridge
|
|
|
|
The Rust wrapper (`src/lib.rs`) exposes these JNI functions to Kotlin:
|
|
|
|
| JNI function | Kotlin | Purpose |
|
|
|---|---|---|
|
|
| `initialize(dataDir)` | `ArtiNative.initialize()` | Create TorClient, bootstrap Tor network |
|
|
| `startSocksProxy(port)` | `ArtiNative.startSocksProxy()` | Bind SOCKS5 listener on localhost |
|
|
| `stopSocksProxy()` | `ArtiNative.stopSocksProxy()` | Abort listener, release port |
|
|
| `getVersion()` | `ArtiNative.getVersion()` | Return Arti version string |
|
|
| `setLogCallback(cb)` | `ArtiNative.setLogCallback()` | Register log callback |
|
|
|
|
### Key design decisions
|
|
|
|
- **TorClient is created once** via `initialize()` and persists for the app's lifetime.
|
|
Its state file lock is tied to the object's lifetime and released only on GC/process exit.
|
|
- **`stopSocksProxy()` only stops the TCP listener** — it does NOT destroy the TorClient.
|
|
This allows clean stop/start cycles without state file lock conflicts.
|
|
- **SOCKS5 is implemented in Rust** using `tokio::net::TcpListener`, not delegated to Arti's
|
|
built-in proxy. This gives us full control over the listener lifecycle.
|
|
- **Bidirectional forwarding** uses `tokio::io::copy` with `tokio::select!` for efficiency.
|
|
|
|
## Cargo.toml features
|
|
|
|
Default features are disabled (`default-features = false`) to minimize binary size.
|
|
|
|
| Feature | Purpose | Why included |
|
|
|---|---|---|
|
|
| `tokio` | Async runtime | Required by our SOCKS proxy |
|
|
| `rustls` | TLS via pure Rust | No OpenSSL dependency, smaller binary |
|
|
| `compression` | zstd/deflate relay traffic | Reduces bandwidth on Tor circuits |
|
|
| `onion-service-client` | Access .onion addresses | Amethyst routes .onion relay connections through Tor |
|
|
| `static-sqlite` | Bundled SQLite | Android native code can't use system SQLite |
|
|
|
|
**Not included:**
|
|
|
|
| Feature | Why excluded |
|
|
|---|---|
|
|
| `native-tls` | Using `rustls` instead (smaller, no system dependency) |
|
|
| `bridge-client` | Amethyst doesn't expose bridge configuration in UI yet. Add back if needed. |
|
|
| `pt-client` | Pluggable transports — same reason as bridges |
|
|
| `onion-service-service` | We only connect to .onion, we don't host them |
|
|
|
|
### Release profile
|
|
|
|
```toml
|
|
[profile.release]
|
|
opt-level = "z" # Optimize for size
|
|
lto = true # Link-time optimization
|
|
codegen-units = 1 # Single codegen unit (smaller binary)
|
|
strip = true # Strip debug symbols
|
|
panic = "abort" # No unwinding (smaller binary)
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### `cargo-ndk` not found
|
|
```bash
|
|
cargo install cargo-ndk
|
|
```
|
|
|
|
### NDK not found
|
|
```bash
|
|
export ANDROID_NDK_HOME="$HOME/Android/Sdk/ndk/<version>"
|
|
```
|
|
|
|
### Rust targets not installed
|
|
```bash
|
|
rustup target add aarch64-linux-android x86_64-linux-android
|
|
```
|
|
|
|
### Build fails with dependency errors
|
|
Try a clean build:
|
|
```bash
|
|
./build-arti.sh --clean
|
|
```
|
|
|
|
### JNI symbols missing after build
|
|
The build script verifies symbols automatically. If verification fails, check that
|
|
`src/lib.rs` function names match the Kotlin package path:
|
|
```
|
|
Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_<methodName>
|
|
```
|