diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 23bf4e9b..08691c77 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.github.shadowsocks" android:installLocation="internalOnly"> + diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt b/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt index cfba6660..1cbdd618 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt @@ -20,12 +20,16 @@ package com.github.shadowsocks.bg +import android.annotation.TargetApi import android.app.Service +import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.LocalSocket +import android.net.* +import android.os.Build import android.os.IBinder import android.os.ParcelFileDescriptor +import android.support.v4.os.BuildCompat import android.util.Log import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.JniHelper @@ -50,6 +54,20 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { private const val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s" private val getInt: Method = FileDescriptor::class.java.getDeclaredMethod("getInt$") + + /** + * Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e + * + * This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that + * satisfies default network capabilities but only THE default network. Unfortunately we need to have + * android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork. + * + * Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887 + */ + private val defaultNetworkRequest = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .build() } private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") { @@ -90,6 +108,22 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { private var worker: ProtectWorker? = null private var tun2socksProcess: GuardedProcess? = null + private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } + @TargetApi(Build.VERSION_CODES.P) + private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + setUnderlyingNetworks(arrayOf(network)) + } + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) { + // it's a good idea to refresh capabilities + setUnderlyingNetworks(arrayOf(network)) + } + override fun onLost(network: Network) { + setUnderlyingNetworks(null) + } + } + private var listeningForDefaultNetwork = false + override fun onBind(intent: Intent): IBinder? = when (intent.action) { SERVICE_INTERFACE -> super.onBind(intent) else -> super.onBind(intent) @@ -98,6 +132,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { override fun onRevoke() = stopRunner(true) override fun killProcesses() { + if (BuildCompat.isAtLeastP() && listeningForDefaultNetwork) { + connectivity.unregisterNetworkCallback(defaultNetworkCallback) + listeningForDefaultNetwork = false + } worker?.stopThread() worker = null super.killProcesses() @@ -179,6 +217,13 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { this.conn = conn val fd = conn.fd + // We only need to update since Android P: https://android.googlesource.com/platform/frameworks/base/+/72f9c42b9e59761a28d6b32c42f65de57c98daed + if (BuildCompat.isAtLeastP()) { + // we want REQUEST here instead of LISTEN + connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback) + listeningForDefaultNetwork = true + } + val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath, "--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"), "--netif-netmask", "255.255.255.0",