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
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build
import android.os.UserManager
import androidx.annotation.RequiresApi
......@@ -59,6 +60,7 @@ object Core {
lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
......
......@@ -27,6 +27,7 @@ import android.content.IntentFilter
import android.os.*
import android.util.Log
import androidx.core.content.getSystemService
import androidx.core.os.BuildCompat
import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
......@@ -289,7 +290,12 @@ object BaseService {
}
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()
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 {
}
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 startProcesses() {
......
......@@ -26,9 +26,8 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.Core
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
......@@ -84,7 +83,7 @@ object DefaultNetworkListener {
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener))
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 {
networkActor.send(this)
response.await()
......@@ -102,7 +101,6 @@ object DefaultNetworkListener {
}
private var fallback = false
private val connectivity = app.getSystemService<ConnectivityManager>()!!
private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
......@@ -119,16 +117,16 @@ object DefaultNetworkListener {
*/
private fun register() {
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
connectivity.registerDefaultNetworkCallback(Callback)
Core.connectivity.registerDefaultNetworkCallback(Callback)
} else try {
fallback = false
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(request, Callback)
Core.connectivity.requestNetwork(request, Callback)
} catch (e: SecurityException) {
// known bug: https://stackoverflow.com/a/33509180/2245107
if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e)
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