Skip to content

ShadowSocks support - #1688

Merged
bitmold merged 15 commits into
guardianproject:masterfrom
tladesignz:shadowsocks
May 12, 2026
Merged

bitmold merged 15 commits into
guardianproject:masterfrom
tladesignz:shadowsocks

Conversation

@tladesignz

Copy link
Copy Markdown
Collaborator

This implements #1684.

The ShadowSocks Rust binary is packaged as a pseudo-library and executed as a subprocess.

The binaries are provided within this commit as the build process is rather involved and cannot be reproduced within our project, as the used "org.mozilla.rust-android-gradle.rust-android" Gradle plugin is outdated and does not support Gradle 9 and newer.

Unfortunately, there's currently no implementation of a ShadowSocks client which exposes a proper library API and isn't super-complicated. (Like Outline's is.)

The configuration is super simple:

Select "ShadowSocks" as a proxy, copy the ss://… URL into the now showing config entry field. Start Tor.

I explicitly didn't implement single fields for each SS proxy argument, as typically the keys are auto-generated and super complicated to type, and the encryption options are many and not easy to understand.

The easiest thing to get such a config URL into that entry field is by using a QR code, scanning it and copy-paste it.

@tladesignz
tladesignz requested review from bitmold and n8fr8 May 8, 2026 15:36
@syphyr

syphyr commented May 8, 2026

Copy link
Copy Markdown
Contributor

Even if we can not build the rust binaries within Orbot, could you still give more details on how its built?

@syphyr

syphyr commented May 9, 2026 •

Copy link
Copy Markdown
Contributor

I think under Outbound Proxy Type, there should be an additional default radio button set to disabled.

@bitmold

bitmold commented May 10, 2026 •

Copy link
Copy Markdown
Collaborator

Even if we can not build the rust binaries within Orbot, could you still give more details on how its built?

@tladesignz i also would like to see this, could you please commit a markdown file (or something similar) that explains the process you used.

EDIT: I just added a script build_shadowsocks_binaries.sh to build libsslocal.so if desired. (cc @syphyr )

Comment thread app/src/main/java/org/torproject/android/service/tor/ShadowSocks.kt
@bitmold

bitmold commented May 10, 2026

Copy link
Copy Markdown
Collaborator

I made some small UI tweaks to the dialog...

@bitmold

bitmold commented May 10, 2026

Copy link
Copy Markdown
Collaborator

Since we are including the binaries directly, I made sure that the new libsslocal.so files were 16KB aligned which is a new restriction imposed by google... they pass. But just for posterity this can be run with this file: https://cs.android.com/android/platform/superproject/main/+/main:system/extras/tools/check_elf_alignment.sh

./check_elf_alignment.sh ~/gp/orbot/app/build/outputs/apk/fullperm/debug/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug.apk

Recursively analyzing /home/a/gp/orbot/app/build/outputs/apk/fullperm/debug/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug.apk

NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher.
  You can install the latest build-tools by running the below command
  and updating your $PATH:

    sdkmanager "build-tools;35.0.0-rc3"

=== ELF alignment ===
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libtor.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libtoolChecker.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libsurface_util_jni.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libsslocal.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libimage_processing_util_jni.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libhev-socks5-tunnel.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libgojni.so: ALIGNED (2**14)
/tmp/Orbot-17.9.4-BETA-1-tor-0.4.9.6.2-23-g9dadf65a1-fullperm-arm64-v8a-debug_out_zBKbb/lib/arm64-v8a/libbarhopper_v3.so: ALIGNED (2**14)
ELF Verification Successful
=====================

@bitmold

bitmold commented May 10, 2026

Copy link
Copy Markdown
Collaborator

Well this works, but yeah that approach is unfortunately pretty bad (to no fault of your own). Seems as if the rust-android-gradle plugin has been neglected. There's an open PR to support gradle 9 mozilla/rust-android-gradle#168

But yeah, this works. I tweaked some small UI stuff, made a script to compile the rust code just to provide a way to get the binaries should we never need to modify them, and tested around it a bit. Feel free to merge it whenever you're ready.

@syphyr

syphyr commented May 11, 2026

Copy link
Copy Markdown
Contributor

@bitmold I've made a few adjustments to the build script.
a) check for shadowsocks-android directory before trying to clone it
b) Only build the rust libraries (libsslocal.so). No need to build the entire project.
c) Make the path relative when copying the libraries

diff --git a/build_shadowsocks_binaries.sh b/build_shadowsocks_binaries.sh
index 6b2f039c0..90b346573 100755
--- a/build_shadowsocks_binaries.sh
+++ b/build_shadowsocks_binaries.sh
@@ -12,8 +12,13 @@
 
 echo "obtaining shadowsocks-android..."
 cd ..
-git clone https://github.com/bitmold/shadowsocks-android
+
+if [ ! -d shadowsocks-android ]; then
+   git clone --recurse-submodules https://github.com/bitmold/shadowsocks-android
+fi
+
 cd shadowsocks-android
+git fetch
 git submodule update --init --recursive
 
 echo ""
@@ -26,7 +31,7 @@ rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-andro
 
 echo ""
 echo "building shadowsocks-android..."
-./gradlew assembleRelease
+./gradlew mergeReleaseJniLibFolders
 
 cd core/build/rustJniLibs/android/
 echo "built shadowsocks-android binaries:"
@@ -35,8 +40,8 @@ ls -al *
 echo ""
 read -p "do you want to overwrite the binaries in orbot? (yes/no) " yn
 if [ "$yn" = "yes" ]; then
-       mv arm64-v8a/libsslocal.so ~/gp/orbot/app/src/main/jniLibs/arm64-v8a/libsslocal.so 
-       mv armeabi-v7a/libsslocal.so ~/gp/orbot/app/src/main/jniLibs/armeabi-v7a/libsslocal.so 
-       mv x86/libsslocal.so ~/gp/orbot/app/src/main/jniLibs/x86/libsslocal.so 
-       mv x86_64/libsslocal.so ~/gp/orbot/app/src/main/jniLibs/x86_64/libsslocal.so 
+       cp -a arm64-v8a ../../../../../orbot/app/src/main/jniLibs/
+       cp -a armeabi-v7a ../../../../../orbot/app/src/main/jniLibs/
+       cp -a x86 ../../../../../orbot/app/src/main/jniLibs/
+       cp -a x86_64 ../../../../../orbot/app/src/main/jniLibs/
 fi

syphyr@e6a5ad0

@syphyr

syphyr commented May 11, 2026

Copy link
Copy Markdown
Contributor

When initially setting up an Outbound Proxy, there are no default types set. When the user wants to disable the Outbound Proxy, they must remove the host? Would it not be better if there was a way to just enable and disable the Outbound Proxy via the Type without having to remove the host?

Comment thread app/src/main/res/values/strings.xml
@tladesignz

Copy link
Copy Markdown
Collaborator Author

Even if we can not build the rust binaries within Orbot, could you still give more details on how its built?

Well, it was documented somewhat inside the ShadowSocks object.
Anyhow, @bitmold took it upon themselves to produce a build script.

I think under Outbound Proxy Type, there should be an additional default radio button set to disabled.

True that. I'll add it.

@bitmold I've made a few adjustments to the build script.

These are all viable improvements. I'll add them.

I currently have one problem with the build script: The fork? Why? Does the elevated minimal API seriously justify decoupling from the development of the original?

I feel, not. Please let me know, what the reasoning behind that was, @bitmold.

…ries:

- Improved robustness.
- Do not execute rustup-install script from foreign location, let user decide where to get rustup.
- Shallow clone *original* source.
- Simplified result copy.
- Use log file for build command output.
@tladesignz

Copy link
Copy Markdown
Collaborator Author

Regarding my build script changes:

  • Rustup should not be installed by the script automatically. Calling a shell script from a remote server looks especially dangerous. Besides, I do get my rustup from homebrew.

  • To make the build script more robust in different environments , I added path evaluation and fully-qualified paths where possible. It also now uses a temporary folder to build in, to have the build environment in a reproducible state.

  • I removed the "are you sure to overwrite" question. This should always be executed in a git environment. If not, well… wtf why?

tladesignz and others added 3 commits May 12, 2026 13:43
…oxy_type` selection, to be able to switch off the proxy without having to edit the host or shadowsocks URL fields.
@bitmold

bitmold commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Regarding my build script changes:

  • Rustup should not be installed by the script automatically. Calling a shell script from a remote server looks especially dangerous. Besides, I do get my rustup from homebrew.
  • To make the build script more robust in different environments , I added path evaluation and fully-qualified paths where possible. It also now uses a temporary folder to build in, to have the build environment in a reproducible state.
  • I removed the "are you sure to overwrite" question. This should always be executed in a git environment. If not, well… wtf why?

all reasonable, was mostly just trying to get my bearings with it.

@bitmold

bitmold commented May 12, 2026 •

Copy link
Copy Markdown
Collaborator

I currently have one problem with the build script: The fork? Why? Does the elevated minimal API seriously justify decoupling from the development of the original?

The fork is not needed, thanks for reverting it back to the canonical repository. at the time I was toying with trying to make modifications to integrate shadowsocks-android with gradle (on my fork) but quickly saw that this was nontrivial, my fork in the script was leftover from that aborted experiment. We can use the canonical shadowsocks-android.

It's worth noting here for posterity that we are hardly using shadowsocks-android at all in this process. The script uses shadowsocks-android simply as a means to build binary blobs of shadowsocks-rust. When you do ./gradlew assembleRelease on shadowsocks-android it does a few tricks to both build the binary objects and also strip them of debugging symbols (making them go from ~100MB to ~5MB).

In a slightly-more ideal world, Orbot would just have shadowsocks-rust as a submodule, and part of our build process would include making these binaries in the same way that we make binaries for hev-socks5-tunnel. This would be trivial to do once there's a release of the mozilla rust-android-gradle plugin compatible with gradle 9. There's a PR for it that has been collecting dust and if the day ever comes wehre something like this is supported, we should remove these binary blobs from our repo and switch to doing something like this.

@tladesignz

Copy link
Copy Markdown
Collaborator Author

It's worth noting here for posterity that we are hardly using shadowsocks-android at all in this process. The script uses shadowsocks-android simply as a means to build binary blobs of shadowsocks-rust. When you do ./gradlew assembleRelease on shadowsocks-android it does a few tricks to both build the binary objects and also strip them of debugging symbols (making them go from ~100MB to ~5MB).

In a slightly-more ideal world, Orbot would just have shadowsocks-rust as a submodule, and part of our build process would include making these binaries in the same way that we make binaries for hev-socks5-tunnel. This would be trivial to do once there's a release of the mozilla rust-android-gradle plugin compatible with gradle 9. There's a PR for it that has been collecting dust and if the day ever comes wehre something like this is supported, we should remove these binary blobs from our repo and switch to doing something like this.

Positively, yes. The architecture of having the build system for one target of a lib residing in another repo is plain stupid, but I'd rather want to wait until somebody other will do the annoying work of fixing this.

@syphyr

syphyr commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

./build_shadowsocks.sh: 11: set: Illegal option -o pipefail

I just changed it to:

-#!/bin/sh
+#!/bin/bash

Edit: The issue is actually that pipefail is a bash only feature and we set #!/bin/sh which may not be directly linked to bash.

@bitmold
bitmold merged commit f3d69a2 into guardianproject:master May 12, 2026
@tladesignz

Copy link
Copy Markdown
Collaborator Author

./build_shadowsocks.sh: 11: set: Illegal option -o pipefail

I just changed it to:

-#!/bin/sh +#!/bin/bash

Edit: The issue is actually that pipefail is a bash only feature and we set #!/bin/sh which may not be directly linked to bash.

Sorry! Worked on my machine. 🤷🥴

@tladesignz
tladesignz deleted the shadowsocks branch May 13, 2026 08:50
bitmold added a commit that referenced this pull request May 17, 2026
bitmold added a commit to tladesignz/orbot that referenced this pull request May 18, 2026
… have an outbound proxy set and connect with obsf4
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.

3 participants