|
| 1 | +# libbyedpi-android |
| 2 | + |
| 3 | +Standalone Android library that embeds **ByeDPI** ([hufrea/byedpi](https://github.com/hufrea/byedpi)) as a `.so` per ABI. Suitable for any Android app that needs DPI-bypass functionality without bundling the full ByeByeDPI APK. |
| 4 | + |
| 5 | +**First public MIT-licensed standalone Android byedpi binding.** The upstream byedpi project itself ships only Linux/Windows/macOS binaries — no Android library. Existing Android consumers ([ByeByeDPI](https://github.com/dovecoteescapee/byedpiandroid), [ByeByeDPI fork](https://github.com/romanvht/ByeByeDPI)) are full apps with their own JNI shim, not reusable libraries. This repo fills that gap. |
| 6 | + |
| 7 | +## What's in each release |
| 8 | + |
| 9 | +Each tag attaches three files to its GitHub Release: |
| 10 | + |
| 11 | +| File | Description | |
| 12 | +|---|---| |
| 13 | +| `libbyedpi-android-v0.1.0.aar` | Android library, all three ABIs bundled (arm64-v8a + armeabi-v7a + x86_64) plus the Kotlin stub | |
| 14 | +| `libbyedpi-android-v0.1.0.aar.sha256` | SHA-256 checksum | |
| 15 | +| `libbyedpi-android-v0.1.0.aar.asc` | GPG detached signature | |
| 16 | + |
| 17 | +Verify before consuming: |
| 18 | + |
| 19 | +```sh |
| 20 | +sha256sum -c libbyedpi-android-v0.1.0.aar.sha256 |
| 21 | + |
| 22 | +# One-time: import maintainer public key from this repo |
| 23 | +gpg --import oviron-signing.pub.asc |
| 24 | +gpg --verify libbyedpi-android-v0.1.0.aar.asc libbyedpi-android-v0.1.0.aar |
| 25 | +# Expected: Good signature from "oviron <awdonkin@gmail.com>" |
| 26 | +``` |
| 27 | + |
| 28 | +Public key fingerprint: `1139 C91B 6525 883E 6783 DCF0 4A94 DA48 8A4C 5033`. Cross-check this fingerprint against the maintainer's GitHub profile (https://github.com/oviron) or keys.openpgp.org before trusting the key. |
| 29 | + |
| 30 | +## Requirements |
| 31 | + |
| 32 | +- **minSdk 21** (Android 5.0+) — the `.so` files are built with `-DANDROID_PLATFORM=android-21`. Older devices will fail at `System.loadLibrary` time. |
| 33 | +- **AGP 8.5.1+** — needed so the host APK is itself 16 KB page-aligned on Android 15+ devices. The `.so` files in this library are already built 16 KB-aligned by NDK 28. |
| 34 | + |
| 35 | +## Integration |
| 36 | + |
| 37 | +### Gradle (file dependency) |
| 38 | + |
| 39 | +Download the `.aar` once during your build and reference it as a file dependency. Example: |
| 40 | + |
| 41 | +```kotlin |
| 42 | +// app/build.gradle.kts |
| 43 | +val byedpiVersion = "0.1.0" |
| 44 | +val byedpiAar = layout.buildDirectory.file("libs/libbyedpi-android-v$byedpiVersion.aar") |
| 45 | + |
| 46 | +val downloadByedpi = tasks.register("downloadByedpi") { |
| 47 | + inputs.property("byedpiVersion", byedpiVersion) |
| 48 | + outputs.file(byedpiAar) |
| 49 | + doLast { |
| 50 | + val target = byedpiAar.get().asFile |
| 51 | + target.parentFile.mkdirs() |
| 52 | + val url = "https://github.com/oviron/libbyedpi-android/releases/download/v$byedpiVersion/libbyedpi-android-v$byedpiVersion.aar" |
| 53 | + target.outputStream().use { out -> |
| 54 | + uri(url).toURL().openStream().use { it.copyTo(out) } |
| 55 | + } |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +dependencies { |
| 60 | + implementation(files(byedpiAar).builtBy(downloadByedpi)) |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +The `.aar` already includes the Kotlin stub `io.github.oviron.libbyedpi.ByeDpi` with `System.loadLibrary("byedpi")` wired up — no additional source code on your side is needed. |
| 65 | + |
| 66 | +### Usage from Kotlin |
| 67 | + |
| 68 | +```kotlin |
| 69 | +import io.github.oviron.libbyedpi.ByeDpi |
| 70 | + |
| 71 | +// On a background thread (the call blocks until the proxy stops): |
| 72 | +val exit = ByeDpi.nativeStart(arrayOf( |
| 73 | + "byedpi", "-i", "127.0.0.1", "-p", "1080", "--auto=torst", |
| 74 | +)) |
| 75 | + |
| 76 | +// From any thread, to signal graceful shutdown: |
| 77 | +ByeDpi.nativeStop() |
| 78 | + |
| 79 | +// Escape hatch — when the proxy is stuck and graceful stop did not |
| 80 | +// unblock the running nativeStart, force-close the listening socket: |
| 81 | +ByeDpi.nativeForceClose() |
| 82 | +``` |
| 83 | + |
| 84 | +The argv-style API mirrors the byedpi CLI 1:1. A typed Kotlin `Config` + `sealed class Strategy` DSL is on the v0.2 roadmap; see the "API maturity" section below. |
| 85 | + |
| 86 | +### End-to-end usage |
| 87 | + |
| 88 | +A minimal lifecycle for a foreground service that hosts the proxy: |
| 89 | + |
| 90 | +```kotlin |
| 91 | +class ByeDpiService : Service() { |
| 92 | + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) |
| 93 | + private var job: Job? = null |
| 94 | + |
| 95 | + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
| 96 | + startForeground(NOTIFICATION_ID, buildNotification()) |
| 97 | + job = scope.launch { |
| 98 | + val exit = ByeDpi.nativeStart(arrayOf( |
| 99 | + "byedpi", "-i", "127.0.0.1", "-p", "1080", "--auto=torst", |
| 100 | + )) |
| 101 | + if (exit != 0) Log.w(TAG, "byedpi exited with code $exit") |
| 102 | + } |
| 103 | + return START_STICKY |
| 104 | + } |
| 105 | + |
| 106 | + override fun onDestroy() { |
| 107 | + ByeDpi.nativeStop() |
| 108 | + runBlocking { job?.join() } |
| 109 | + scope.cancel() |
| 110 | + super.onDestroy() |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +Other components in your app then route traffic through the SOCKS5 endpoint at `127.0.0.1:1080` (for example by configuring a `Proxy` on `OkHttpClient.Builder` or by feeding it into a VPN service `tun` socket protector). |
| 116 | + |
| 117 | +## CLI arguments |
| 118 | + |
| 119 | +`nativeStart(args)` accepts the same CLI as upstream byedpi binary. First element is conventionally `"byedpi"` (argv[0] / program name). See [hufrea/byedpi README](https://github.com/hufrea/byedpi#readme) for the full option list. |
| 120 | + |
| 121 | +Common arguments: |
| 122 | + |
| 123 | +| Flag | Effect | |
| 124 | +|---|---| |
| 125 | +| `-i 127.0.0.1` | Bind address | |
| 126 | +| `-p 1080` | SOCKS5 listen port | |
| 127 | +| `--auto=torst` | Auto-detect best DPI-bypass strategy (default) | |
| 128 | +| `--no-domain` | Disable hostname check | |
| 129 | +| `--hosts FILE` | Whitelist mode: only bypass DPI for these hosts | |
| 130 | +| `-d` | Increase debug verbosity | |
| 131 | + |
| 132 | +## Troubleshooting |
| 133 | + |
| 134 | +| Symptom | Likely cause | Fix | |
| 135 | +|---|---|---| |
| 136 | +| `nativeStart` returns immediately with non-zero | Bind port already in use (another proxy app, or previous instance still listening) | Pick a different `-p` value, or call `nativeStop` and wait for the previous `nativeStart` to return before re-starting | |
| 137 | +| `nativeStart` returns -1 instantly | Another call to `nativeStart` is already in flight on the same process | Wait for the running call to return; only one proxy per process is supported | |
| 138 | +| ANR on main thread | `nativeStart` called from main thread | Always call from `Dispatchers.IO` or another background thread — `nativeStart` blocks until `nativeStop` is signalled | |
| 139 | +| Crash on `System.loadLibrary` | Missing ABI for device, or `minSdk < 21` | Verify the consumer app ships all three ABIs (`arm64-v8a`, `armeabi-v7a`, `x86_64`) and sets `minSdk = 21` | |
| 140 | +| Traffic does not bypass DPI | Wrong byedpi strategy for the operator | Try `--auto=torst` (default), then experiment with explicit flags from the [byedpi README](https://github.com/hufrea/byedpi#readme) | |
| 141 | +| App lacks network access while proxy runs | App is missing `<uses-permission android:name="android.permission.INTERNET"/>` | Add it to your manifest | |
| 142 | + |
| 143 | +## License |
| 144 | + |
| 145 | +This wrapper code (`src/cpp/native-lib.c`, `src/cpp/main.h`, `src/cpp/CMakeLists.txt`) is MIT-licensed — see `LICENSE`. |
| 146 | + |
| 147 | +The bundled ByeDPI source at `src/cpp/byedpi/` is also MIT-licensed by hufrea — see `src/cpp/byedpi/LICENSE`. |
| 148 | + |
| 149 | +Downstream apps can be **any** license, including proprietary. There is no copyleft obligation propagated through this library. |
| 150 | + |
| 151 | +## API maturity |
| 152 | + |
| 153 | +v0.1.0 is a **low-level JNI binding**. The `nativeStart` method takes a raw argv-style `Array<String>` matching byedpi's CLI surface 1:1. This is intentional — it keeps the wrapper thin, predictable, and trivially in sync with upstream byedpi. The cost is consumer-side boilerplate: you build the argv yourself from typed values. |
| 154 | + |
| 155 | +A higher-level typed Kotlin API (data-class `Config`, `sealed class Strategy`, builder DSL, `.aar` packaging) is planned for **v0.2**. v0.1.0 is shipped first so that FlClash and other early adopters can integrate today and inform what the typed API should look like based on real usage. |
| 156 | + |
| 157 | +## Compatibility note |
| 158 | + |
| 159 | +While the library version is below 1.0, treat the **whole library as API-unstable**: bundled byedpi version, CLI flag semantics, and library packaging may change between minor releases. The two JNI method signatures (`nativeStart(Array<String>): Int`, `nativeStop(): Int`) are not expected to change during 0.x, but no guarantee is made. **Pin an exact version in your build** (`v0.1.0`, not `v0.1.+`) and re-read the CHANGELOG before bumping. |
| 160 | + |
| 161 | +Semver guarantees begin at v1.0. |
| 162 | + |
| 163 | +## Building from source |
| 164 | + |
| 165 | +```sh |
| 166 | +git clone https://github.com/oviron/libbyedpi-android.git |
| 167 | +cd libbyedpi-android |
| 168 | +git submodule update --init --recursive |
| 169 | + |
| 170 | +# Build one ABI (requires Android NDK 28+): |
| 171 | +cmake -B build-arm64 \ |
| 172 | + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \ |
| 173 | + -DANDROID_ABI=arm64-v8a \ |
| 174 | + -DANDROID_PLATFORM=android-21 \ |
| 175 | + -DCMAKE_BUILD_TYPE=Release \ |
| 176 | + -S src/cpp |
| 177 | +cmake --build build-arm64 |
| 178 | +# Output: build-arm64/libbyedpi.so |
| 179 | +``` |
| 180 | + |
| 181 | +See `MAINTENANCE.md` for the full release procedure. |
| 182 | + |
| 183 | +## Reporting issues |
| 184 | + |
| 185 | +GitHub Issues for bugs and feature requests. For security disclosures see `SECURITY.md`. |
0 commit comments