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..4e3e0989 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt @@ -21,9 +21,11 @@ package com.github.shadowsocks.bg 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.util.Log @@ -50,6 +52,21 @@ 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 VPN interface since Android P DP1: + * 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") { @@ -59,13 +76,22 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { var success = false try { socket.inputStream.read() - val fds = socket.ancillaryFileDescriptors - if (fds.isEmpty()) return - val fd = getInt.invoke(fds.first()) as Int - success = protect(fd) - JniHelper.close(fd) // Trick to close file decriptor + val fd = socket.ancillaryFileDescriptors!!.single()!! + val fdInt = getInt.invoke(fd) as Int + try { + val network = underlyingNetwork + success = if (network != null && Build.VERSION.SDK_INT >= 23) { + network.bindSocket(fd) + true + } else protect(fdInt) + } catch (e: Exception) { + Log.e(tag, "Error when protect socket", e) + app.track(e) + } finally { + JniHelper.close(fdInt) // Trick to close file decriptor + } } catch (e: Exception) { - Log.e(tag, "Error when protect socket", e) + Log.e(tag, "Error when receiving ancillary fd", e) app.track(e) } try { @@ -89,6 +115,26 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { private var conn: ParcelFileDescriptor? = null private var worker: ProtectWorker? = null private var tun2socksProcess: GuardedProcess? = null + private var underlyingNetwork: Network? = null + set(value) { + if (Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(if (value == null) null else arrayOf(value)) + field = value + } + + private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } + private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + underlyingNetwork = network + } + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) { + // it's a good idea to refresh capabilities + underlyingNetwork = network + } + override fun onLost(network: Network) { + underlyingNetwork = null + } + } + private var listeningForDefaultNetwork = false override fun onBind(intent: Intent): IBinder? = when (intent.action) { SERVICE_INTERFACE -> super.onBind(intent) @@ -98,6 +144,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { override fun onRevoke() = stopRunner(true) override fun killProcesses() { + if (listeningForDefaultNetwork) { + connectivity.unregisterNetworkCallback(defaultNetworkCallback) + listeningForDefaultNetwork = false + } worker?.stopThread() worker = null super.killProcesses() @@ -179,6 +229,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { this.conn = conn val fd = conn.fd + // 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",