shadowsocks / shadowsocks-android

A shadowsocks client for Android
Other
35.23k stars 11.57k forks source link

Proposal: Calling `VpnService.protect` directly via JNI #2761

Closed zonyitoo closed 3 years ago

zonyitoo commented 3 years ago

Is your feature request related to a problem? Please describe.

shadowsocks-android and the underlying shadowsocks-rust are communicating via an UDS for protecting socket file descriptors by calling VpnService.protect, which is not elegant and causing some hard to reproduce problems.

Describe the solution you'd like

I got this idea from a Telegram Group. The original author have his copyright.

  1. The VpnService.protect calls a C function protectFromVpn in NetdClient.h . Are there any chances that we can call this function directly? But the problem is how to link to netd when compiling.
  2. Compile sslocal as a .so and expose two start_local, stop_local functions via JNI. When the .so is loaded, JNI_OnLoad will be called by JVM, then we can load the Landroid/net/NetworkUtils's protectFromVpn function and then call it when creating outbound sockets.

Describe alternatives you've considered

No.

Additional context

No.

zonyitoo commented 3 years ago

The 2nd way should be practical, because:

  1. Run System.loadLibrary on the compiled .so will call JNI_OnLoad.
  2. Rust's jni crate provides basic wrappers of JNI's structs. Get the MethodID of NetworkUtils.protectFromVpn like this.
  3. Implementation details:
    • start_local: Create a tokio::runtime::Runtime and spawns the run_local entry Future into it. Allocate a global instance array and find an empty slot for putting the Runtime in it. Returns the index of the slot as the instance ID.
    • stop_local: Accepts an instance ID and find the Runtime from the instance array, take it out and call shutdown_backgroud for killing the whole server.
Mygod commented 3 years ago

The API you proposed are not public API and I would advise against using it. Furthermore, NetworkUtils.protectFromVpn is blocked when targeting Android 12: https://developer.android.com/about/versions/12/non-sdk-12#new-blocked

Furthermore, in order to invoke a java method, you would first need to start up the JVM/app_process and then load Rust code as JNI. This creates huge memory overhead.

Finally, shadowsocks-android uses Network.bindSocket along with ConnectivityManager APIs to manage connection binding. VpnService.protect is only used for fallback.

See: https://github.com/shadowsocks/shadowsocks-android/blob/f5bc88b83ebbd4d2941a65bea2eaa565f2072db4/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt#L77-L90

Mygod commented 3 years ago

As for your second solution, I think @madeye intentionally designed it to have separately running binaries.

madeye commented 3 years ago

I had a try years ago by using the JNI based protect() from golang: https://github.com/madeye/BaoLianDeng/blob/master/app/src/main/java/io/github/baoliandeng/core/LocalVpnService.java#L215

And as mentioned by @Mygod, the memory usage is huge... So I never did this in shadowsocks-android.

nekohasekai commented 3 years ago

blocked when targeting Android 12

The blocked method is protectFromVpn(FileDescriptor) instead of protectFromVpn(int).

image

In addition, this restriction can be bypassed

uses Network.bindSocket

If you hate the non-standard usage, you can call your own callback method.

the memory usage is huge

I don't think pure jni calls have "huge" memory usage, which could be a gomobile / memory leak problem.

zonyitoo commented 3 years ago

Maybe we don’t need to start a new JVM to load the Rust code, but just callSystem.loadLibrary in the current process and then call the exposed APIs directly.

Are there any special reasons about using app_process instead of loadLibrary? I am not familiar with Android dev.

madeye commented 3 years ago

First of all, UDS is not a problem. The entire Android system is based on UDS and we never see an issue that is really caused by it in shadowsocks-android.

Secondly, the design choice here for shadowsocks-android is keeping every background service in a separated process, for resource saving and isolation.