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",