Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
769 changes: 332 additions & 437 deletions .github/workflows/android-build.yml

Large diffs are not rendered by default.

142 changes: 93 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,91 +1,135 @@
## INTRO
# ProxyDroid

Global Proxy App for Android System
A global proxy app for Android, modernised around a VPN-first architecture.

ProxyDroid is distributed under GPLv3 with many other open source software,
here is a list of them:
ProxyDroid forwards device traffic to an upstream SOCKS5 or HTTP proxy
without requiring root. It installs a `VpnService`, captures IP packets on
a TUN device, and converts TCP/UDP flows into proxy connections in
userspace.

* redsocks - transparent socks redirector: http://darkk.net.ru/redsocks/
* tun2socks - VPN-based transparent proxy
* netfilter/iptables - NAT module: http://www.netfilter.org/
## ARCHITECTURE

- **VPN-first.** No iptables, no redsocks, no root. The app runs as a
standard Android `VpnService`, which works on every supported Android
release.
- **Rust tun2socks.** The packet-to-socket bridge lives in
`app/src/main/rust/proxydroid-tun2socks`, built with
[netstack-smoltcp](https://crates.io/crates/netstack-smoltcp) and
invoked from Kotlin through JNI. Cargo builds are wired in via
`org.mozilla.rust-android-gradle`.
- **Compose UI.** The settings and status surfaces are written in
Jetpack Compose with Material 3.
- **Per-app bypass.** Apps can be excluded from the tunnel via the
standard `VpnService` `addDisallowedApplication` API.

## SUPPORTED UPSTREAMS

- SOCKS5 (with optional username/password auth)
- HTTP `CONNECT` (with optional Basic auth)

## PREREQUISITES

* JDK 11+
* Android Studio or Gradle 8.1+
* Android SDK (compileSdk 33)
* Android NDK 25.1.8937393
* CMake 3.22.1
- JDK 17 (Gradle 8.x does not accept JDK 21+)
- Android SDK with `compileSdk` 36 installed
- Android NDK `25.1.8937393`
- CMake `3.22.1`
- Rust stable toolchain with the Android targets:
`aarch64-linux-android`, `armv7-linux-androideabi`,
`i686-linux-android`, `x86_64-linux-android`

> AGP is pinned to **8.1.2** and Kotlin to **1.9.10**. See
> `gradle/libs.versions.toml` for every version. Do not bump AGP without
> verifying the `rust-android-gradle` 0.9.6 `mergeJniLibFolders`
> duplicate-resources interaction (cf. commits `0249f91`, `8875c74`).

## BUILD

### Using Android Studio
### Android Studio

1. Open the project in Android Studio
2. Sync Gradle files
3. Build the project using `Build > Make Project`
1. Open the project root.
2. Let Gradle sync — Cargo runs as part of `cargoBuild` and feeds the
JNI libraries into the merged APK.
3. `Build > Make Project`.

### Using Command Line
### Command line

```bash
./gradlew assembleDebug
./gradlew assembleDebug # debug APK at app/build/outputs/apk/debug/
./gradlew assembleRelease # release APK; requires signing config (see below)
```

For release build:
Signing for `release` is opt-in. Add a `local.properties` with:

```bash
./gradlew assembleRelease
```
KEYSTORE_PATH=/absolute/path/to/keystore.jks
KEYSTORE_PASSWORD=...
KEY_ALIAS=...
KEY_PASSWORD=...
```

If those keys are absent the release variant builds unsigned.

## PROJECT STRUCTURE

```
app/
├── src/main/
│ ├── java/org/proxydroid/ # Kotlin source files
│ │ ├── ProxyDroid.kt # Main activity
│ │ ├── ProxyDroidService.kt
│ │ ├── ProxyDroidVpnService.kt
│ │ ├── AppManager.kt
│ │ ├── Profile.kt
│ │ └── utils/ # Utility classes
│ └── cpp/ # Native code
│ ├── exec/ # Native exec helper
│ ├── libevent/ # libevent library
│ ├── redsocks/ # redsocks proxy
│ └── tun2socks/ # tun2socks VPN helper
└── build.gradle
│ ├── java/org/proxydroid/ # Kotlin sources (Compose UI + VpnService)
│ ├── res/ # Resources
│ ├── cpp/ # Native helpers built via CMake
│ │ └── exec/ # termExec — JNI native-process helper
│ ├── rust/
│ │ └── proxydroid-tun2socks/ # Rust tun2socks crate (netstack-smoltcp)
│ └── AndroidManifest.xml
├── build.gradle # App module build (consumes libs.versions.toml)
└── proguard-rules.pro
gradle/
├── libs.versions.toml # Single source of truth for dependency versions
└── wrapper/
build.gradle # Root build, repos + plugin classpath
gradle.properties # JVM args + AGP compileSdk suppression
settings.gradle
scripts/ # Test helpers (Python SOCKS5/HTTP servers, etc.)
```

> Legacy `cpp/libevent` and `cpp/redsocks` directories from the iptables
> era are scheduled for removal in the ongoing refactor. The redsocks
> path is no longer reachable from Kotlin.

## INTEGRATION TEST (EMULATOR ↔ HOST SOCKS5)

`HostSocks5ProxyIntegrationTest` runs inside an Android emulator and routes an
HTTP request through a SOCKS5 proxy listening on the host. The host proxy is a
small stdlib-only Python server in `scripts/socks5_test_server.py`.
`HostSocks5ProxyIntegrationTest` runs inside an Android emulator and
routes an HTTP request through a SOCKS5 proxy listening on the host. The
host proxy is a small stdlib-only Python server in
`scripts/socks5_test_server.py`.

The emulator reaches the host loopback via the alias `10.0.2.2`, so a host
proxy bound to `0.0.0.0:1080` is seen by the device as `10.0.2.2:1080`.
The emulator reaches the host loopback via the alias `10.0.2.2`, so a
host proxy bound to `0.0.0.0:1080` is seen by the device as
`10.0.2.2:1080`.

```bash
# 1. Start the SOCKS5 proxy on the host (terminal 1)
# Terminal 1 — start the SOCKS5 proxy on the host:
python3 scripts/socks5_test_server.py --host 0.0.0.0 --port 1080

# 2. Boot any AVD, then run the instrumentation test (terminal 2)
# Terminal 2 — boot any AVD, then run the instrumentation test:
./gradlew connectedAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=org.proxydroid.HostSocks5ProxyIntegrationTest
```

Override the proxy / target with `-Pandroid.testInstrumentationRunnerArguments.socksHost=...`,
Override defaults with
`-Pandroid.testInstrumentationRunnerArguments.socksHost=...`,
`socksPort`, `targetHost`, `targetPort`.

## SUPPORTED ARCHITECTURES
## SUPPORTED ABIS

* armeabi-v7a
* arm64-v8a
* x86
* x86_64
`armeabi-v7a`, `arm64-v8a`, `x86`, `x86_64`.

## REQUIREMENTS

* Minimum SDK: 21 (Android 5.0)
* Target SDK: 33 (Android 13)
- `minSdk` 24 (Android 7.0)
- `targetSdk` / `compileSdk` 36 (Android 16) — required by Google Play
2025/2026 policy

## LICENSE

GPLv3.
67 changes: 38 additions & 29 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ android {
}

composeOptions {
kotlinCompilerExtensionVersion '1.5.3'
// Must match the Kotlin version (see gradle/libs.versions.toml).
kotlinCompilerExtensionVersion libs.versions.composeCompiler.get()
}

packagingOptions {
Expand All @@ -102,36 +103,44 @@ tasks.whenTaskAdded { task ->
}

dependencies {
// Local JARs (none expected today, kept for parity with legacy layout)
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.10.1'
// Exclude JUnit from json-simple (it pulls junit as compile dependency)
implementation('com.googlecode.json-simple:json-simple:1.1.1') {
exclude group: 'junit', module: 'junit'
}
implementation 'org.mozilla:rhino:1.7.14'

// Compose
def composeBom = platform('androidx.compose:compose-bom:2023.10.01')
// AndroidX core / app shell
implementation libs.androidx.appcompat
implementation libs.androidx.core.ktx
implementation libs.material

// Kotlin
implementation libs.kotlin.stdlib

// Jetpack Compose (BOM pins all artifact versions)
def composeBom = platform(libs.compose.bom)
implementation composeBom
androidTestImplementation composeBom
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material-icons-extended'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation 'androidx.activity:activity-compose:1.8.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.6.2'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.3.1'

// Android instrumented test dependencies
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation libs.compose.material3
implementation libs.compose.material.icons.extended
implementation libs.compose.ui.tooling.preview
debugImplementation libs.compose.ui.tooling
implementation libs.androidx.activity.compose
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.runtime.compose
implementation libs.androidx.lifecycle.runtime.ktx

// Third-party
// json-simple drops JUnit as a compile dep; exclude to avoid runtime classpath pollution.
implementation(libs.json.simple) {
exclude group: 'junit', module: 'junit'
}
implementation libs.rhino

// Unit test
testImplementation libs.junit
testImplementation libs.mockito.core

// Android instrumented test
androidTestImplementation libs.androidx.test.junit
androidTestImplementation libs.androidx.test.runner
androidTestImplementation libs.androidx.test.rules
androidTestImplementation libs.androidx.test.espresso.core
}
11 changes: 8 additions & 3 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
cmake_minimum_required(VERSION 3.22.1)
project(proxydroid_native)

add_subdirectory(exec)
add_subdirectory(libevent)
add_subdirectory(redsocks)
# All legacy native targets (exec/libevent/redsocks) have been removed.
# Native code is now provided by the Rust tun2socks crate, which is
# built by the rust-android-gradle plugin (not by CMake).
#
# A trivial placeholder library is declared so AGP's externalNativeBuild
# CMake task has at least one target to configure successfully.
add_library(proxydroid_native_placeholder STATIC placeholder.c)
8 changes: 0 additions & 8 deletions app/src/main/cpp/exec/CMakeLists.txt

This file was deleted.

Loading
Loading