Commit f141dfb3 authored by Mygod's avatar Mygod

Implement almost fully non-blocking LocalDnsServer

See also: https://issuetracker.google.com/issues/123781253

The only blocking call now is the Java NIO selector.
parent 4dc4b4e2
...@@ -31,6 +31,7 @@ import android.content.Intent ...@@ -31,6 +31,7 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.UserManager import android.os.UserManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
...@@ -59,6 +60,7 @@ object Core { ...@@ -59,6 +60,7 @@ object Core {
lateinit var app: Application lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent lateinit var configureIntent: (Context) -> PendingIntent
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) } val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) } val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) } val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
......
...@@ -27,6 +27,7 @@ import android.content.IntentFilter ...@@ -27,6 +27,7 @@ import android.content.IntentFilter
import android.os.* import android.os.*
import android.util.Log import android.util.Log
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.BuildCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
...@@ -289,7 +290,12 @@ object BaseService { ...@@ -289,7 +290,12 @@ object BaseService {
} }
suspend fun preInit() { } suspend fun preInit() { }
suspend fun resolver(host: String) = InetAddress.getAllByName(host) suspend fun resolver(host: String): Array<InetAddress> {
return if (BuildCompat.isAtLeastQ()) {
// prefer non-blocking version if available
DnsResolverCompat.resolve(Core.connectivity.activeNetwork ?: return emptyArray(), host)
} else InetAddress.getAllByName(host)
}
suspend fun openConnection(url: URL) = url.openConnection() suspend fun openConnection(url: URL) = url.openConnection()
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
......
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.net.DnsResolver
import android.net.Network
import android.os.Handler
import android.os.HandlerThread
import androidx.core.os.BuildCompat
import kotlinx.coroutines.suspendCancellableCoroutine
import java.net.InetAddress
import kotlin.coroutines.resume
sealed class DnsResolverCompat {
companion object : DnsResolverCompat() {
private val instance by lazy { if (BuildCompat.isAtLeastQ()) DnsResolverCompat29() else DnsResolverCompat21 }
override suspend fun resolve(network: Network, host: String) = instance.resolve(network, host)
}
abstract suspend fun resolve(network: Network, host: String): Array<InetAddress>
private object DnsResolverCompat21 : DnsResolverCompat() {
override suspend fun resolve(network: Network, host: String) = network.getAllByName(host)
}
@TargetApi(29)
private class DnsResolverCompat29 : DnsResolverCompat() {
private val handler = Handler(HandlerThread("DnsResolverCompat").run {
start()
looper
})
override suspend fun resolve(network: Network, host: String): Array<InetAddress> {
return suspendCancellableCoroutine { cont ->
// retry should be handled by client instead
DnsResolver.getInstance().query(network, host, DnsResolver.FLAG_NO_RETRY, handler) {
cont.resume(it.toTypedArray())
}
}
}
}
}
...@@ -141,7 +141,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -141,7 +141,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it } override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }
override suspend fun resolver(host: String) = DefaultNetworkListener.get().getAllByName(host) override suspend fun resolver(host: String) = DnsResolverCompat.resolve(DefaultNetworkListener.get(), host)
override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url) override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url)
override suspend fun startProcesses() { override suspend fun startProcesses() {
......
...@@ -26,9 +26,8 @@ import android.net.Network ...@@ -26,9 +26,8 @@ import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
import android.os.Build import android.os.Build
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app import com.github.shadowsocks.Core
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
...@@ -84,7 +83,7 @@ object DefaultNetworkListener { ...@@ -84,7 +83,7 @@ object DefaultNetworkListener {
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener)) suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener))
suspend fun get() = if (fallback) @TargetApi(23) { suspend fun get() = if (fallback) @TargetApi(23) {
connectivity.activeNetwork ?: throw UnknownHostException() // failed to listen, return current if available Core.connectivity.activeNetwork ?: throw UnknownHostException() // failed to listen, return current if available
} else NetworkMessage.Get().run { } else NetworkMessage.Get().run {
networkActor.send(this) networkActor.send(this)
response.await() response.await()
...@@ -102,7 +101,6 @@ object DefaultNetworkListener { ...@@ -102,7 +101,6 @@ object DefaultNetworkListener {
} }
private var fallback = false private var fallback = false
private val connectivity = app.getSystemService<ConnectivityManager>()!!
private val request = NetworkRequest.Builder().apply { private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
...@@ -119,16 +117,16 @@ object DefaultNetworkListener { ...@@ -119,16 +117,16 @@ object DefaultNetworkListener {
*/ */
private fun register() { private fun register() {
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) { if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
connectivity.registerDefaultNetworkCallback(Callback) Core.connectivity.registerDefaultNetworkCallback(Callback)
} else try { } else try {
fallback = false fallback = false
// we want REQUEST here instead of LISTEN // we want REQUEST here instead of LISTEN
connectivity.requestNetwork(request, Callback) Core.connectivity.requestNetwork(request, Callback)
} catch (e: SecurityException) { } catch (e: SecurityException) {
// known bug: https://stackoverflow.com/a/33509180/2245107 // known bug: https://stackoverflow.com/a/33509180/2245107
if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e) if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e)
fallback = true fallback = true
} }
} }
private fun unregister() = connectivity.unregisterNetworkCallback(Callback) private fun unregister() = Core.connectivity.unregisterNetworkCallback(Callback)
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment