Unverified Commit 1dbb37f9 authored by Max Lv's avatar Max Lv Committed by GitHub

Merge pull request #2095 from shadowsocks/dnsproxyd

Use dnsproxyd for DNS resolving
parents a0ae8bf1 27f5c075
package com.github.shadowsocks.utils package com.github.shadowsocks.net
import java.net.InetAddress
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import java.net.InetAddress
class SubnetTest { class SubnetTest {
@Test @Test
......
...@@ -44,6 +44,7 @@ import com.github.shadowsocks.aidl.ShadowsocksConnection ...@@ -44,6 +44,7 @@ import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.* import com.github.shadowsocks.utils.*
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
......
...@@ -25,8 +25,8 @@ import android.util.Log ...@@ -25,8 +25,8 @@ import android.util.Log
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
......
...@@ -42,6 +42,7 @@ import com.github.shadowsocks.utils.printLog ...@@ -42,6 +42,7 @@ import com.github.shadowsocks.utils.printLog
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.net.InetAddress
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.*
...@@ -274,6 +275,9 @@ object BaseService { ...@@ -274,6 +275,9 @@ object BaseService {
} }
} }
suspend fun preInit() { }
suspend fun resolver(host: String) = InetAddress.getByName(host)
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY if (data.state != STOPPED) return Service.START_NOT_STICKY
...@@ -306,10 +310,11 @@ object BaseService { ...@@ -306,10 +310,11 @@ object BaseService {
data.changeState(CONNECTING) data.changeState(CONNECTING)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) { data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
try { try {
proxy.init()
data.udpFallback?.init()
killProcesses() killProcesses()
preInit()
proxy.init(this@Interface::resolver)
data.udpFallback?.init(this@Interface::resolver)
data.processes = GuardedProcessPool { data.processes = GuardedProcessPool {
printLog(it) printLog(it)
data.connectingJob?.apply { runBlocking { cancelAndJoin() } } data.connectingJob?.apply { runBlocking { cancelAndJoin() } }
......
...@@ -116,7 +116,7 @@ class GuardedProcessPool(private val onFatal: (IOException) -> Unit) : Coroutine ...@@ -116,7 +116,7 @@ class GuardedProcessPool(private val onFatal: (IOException) -> Unit) : Coroutine
private val guards = ArrayList<Guard>() private val guards = ArrayList<Guard>()
@MainThread @MainThread
suspend fun start(cmd: List<String>, onRestartCallback: (suspend () -> Unit)? = null) { fun start(cmd: List<String>, onRestartCallback: (suspend () -> Unit)? = null) {
Crashlytics.log(Log.DEBUG, TAG, "start process: " + Commandline.toString(cmd)) Crashlytics.log(Log.DEBUG, TAG, "start process: " + Commandline.toString(cmd))
val guard = Guard(cmd) val guard = Guard(cmd)
guard.start() guard.start()
......
...@@ -52,7 +52,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -52,7 +52,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
private val plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions private val plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions
val pluginPath by lazy { PluginManager.init(plugin) } val pluginPath by lazy { PluginManager.init(plugin) }
suspend fun init() { suspend fun init(resolver: suspend (String) -> InetAddress) {
if (profile.host == "198.199.101.152") { if (profile.host == "198.199.101.152") {
val mdg = MessageDigest.getInstance("SHA-1") val mdg = MessageDigest.getInstance("SHA-1")
mdg.update(Core.packageInfo.signaturesCompat.first().toByteArray()) mdg.update(Core.packageInfo.signaturesCompat.first().toByteArray())
...@@ -84,7 +84,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -84,7 +84,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
// it's hard to resolve DNS on a specific interface so we'll do it here // it's hard to resolve DNS on a specific interface so we'll do it here
if (profile.host.parseNumericAddress() == null) profile.host = withTimeout(10_000) { if (profile.host.parseNumericAddress() == null) profile.host = withTimeout(10_000) {
withContext(Dispatchers.IO) { InetAddress.getByName(profile.host).hostAddress } withContext(Dispatchers.IO) { resolver(profile.host).hostAddress }
} ?: throw UnknownHostException() } ?: throw UnknownHostException()
} }
...@@ -92,7 +92,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -92,7 +92,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
* Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or * Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or
* device storage, depending on which is currently available. * device storage, depending on which is currently available.
*/ */
suspend fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String? = null) { fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String? = null) {
trafficMonitor = TrafficMonitor(stat) trafficMonitor = TrafficMonitor(stat)
this.configFile = configFile this.configFile = configFile
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg ...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.net.LocalSocket import android.net.LocalSocket
import android.os.SystemClock import android.os.SystemClock
import com.github.shadowsocks.aidl.TrafficStats import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.net.LocalSocketListener
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
...@@ -30,7 +31,7 @@ import java.nio.ByteOrder ...@@ -30,7 +31,7 @@ import java.nio.ByteOrder
class TrafficMonitor(statFile: File) : AutoCloseable { class TrafficMonitor(statFile: File) : AutoCloseable {
private val thread = object : LocalSocketListener("TrafficMonitor", statFile) { private val thread = object : LocalSocketListener("TrafficMonitor", statFile) {
override fun accept(socket: LocalSocket) = socket.use { override fun acceptInternal(socket: LocalSocket) {
val buffer = ByteArray(16) val buffer = ByteArray(16)
if (socket.inputStream.read(buffer) != 16) throw IOException("Unexpected traffic stat length") if (socket.inputStream.read(buffer) != 16) throw IOException("Unexpected traffic stat length")
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN) val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
......
...@@ -36,7 +36,7 @@ class TransproxyService : Service(), LocalDnsService.Interface { ...@@ -36,7 +36,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId) super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
private suspend fun startDNSTunnel() { private fun startDNSTunnel() {
val proxy = data.proxy!! val proxy = data.proxy!!
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).absolutePath, val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).absolutePath,
"-t", "10", "-t", "10",
...@@ -50,7 +50,7 @@ class TransproxyService : Service(), LocalDnsService.Interface { ...@@ -50,7 +50,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
data.processes!!.start(cmd) data.processes!!.start(cmd)
} }
private suspend fun startRedsocksDaemon() { private fun startRedsocksDaemon() {
File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base { File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base {
log_debug = off; log_debug = off;
log_info = off; log_info = off;
......
...@@ -20,26 +20,28 @@ ...@@ -20,26 +20,28 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.* import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.os.Build import android.os.Build
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.system.ErrnoException import android.system.ErrnoException
import android.system.Os import android.system.Os
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.VpnRequestActivity import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.DefaultNetworkListener
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.parseNumericAddress import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.* import kotlinx.coroutines.delay
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.io.FileDescriptor import java.io.FileDescriptor
...@@ -58,60 +60,32 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -58,60 +60,32 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
* https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466 * https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466
*/ */
private val getInt: Method = FileDescriptor::class.java.getDeclaredMethod("getInt$") 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()
} }
class CloseableFd(val fd: FileDescriptor) : Closeable { class CloseableFd(val fd: FileDescriptor) : Closeable {
override fun close() = Os.close(fd) override fun close() = Os.close(fd)
} }
private inner class ProtectWorker : private inner class ProtectWorker : ConcurrentLocalSocketListener("ShadowsocksVpnThread",
LocalSocketListener("ShadowsocksVpnThread", File(Core.deviceStorage.noBackupFilesDir, "protect_path")), File(Core.deviceStorage.noBackupFilesDir, "protect_path")) {
CoroutineScope { override fun acceptInternal(socket: LocalSocket) {
private val job = SupervisorJob() socket.inputStream.read()
override val coroutineContext get() = Dispatchers.IO + job + CoroutineExceptionHandler { _, t -> printLog(t) } val fd = socket.ancillaryFileDescriptors!!.single()!!
CloseableFd(fd).use {
override fun accept(socket: LocalSocket) { socket.outputStream.write(if (underlyingNetwork.let { network ->
launch { if (network != null && Build.VERSION.SDK_INT >= 23) try {
socket.use { network.bindSocket(fd)
socket.inputStream.read() true
val fd = socket.ancillaryFileDescriptors!!.single()!! } catch (e: IOException) {
CloseableFd(fd).use { // suppress ENONET (Machine is not on the network)
socket.outputStream.write(if (underlyingNetwork.let { network -> if ((e.cause as? ErrnoException)?.errno != 64) printLog(e)
if (network != null && Build.VERSION.SDK_INT >= 23) try { false
network.bindSocket(fd) } else protect(getInt.invoke(fd) as Int)
true }) 0 else 1)
} catch (e: IOException) {
// suppress ENONET (Machine is not on the network)
if ((e.cause as? ErrnoException)?.errno != 64) printLog(e)
false
} else protect(getInt.invoke(fd) as Int)
}) 0 else 1)
}
}
} }
} }
suspend fun shutdown() {
job.cancel()
close()
job.join()
}
} }
inner class NullConnectionException : NullPointerException() { inner class NullConnectionException : NullPointerException() {
override fun getLocalizedMessage() = getString(R.string.reboot_required) override fun getLocalizedMessage() = getString(R.string.reboot_required)
} }
...@@ -123,28 +97,13 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -123,28 +97,13 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var conn: ParcelFileDescriptor? = null private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null private var worker: ProtectWorker? = null
private var active = false
private var underlyingNetwork: Network? = null private var underlyingNetwork: Network? = null
@TargetApi(24)
set(value) { set(value) {
setUnderlyingNetworks(if (value == null) null else arrayOf(value))
field = value field = value
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
} }
private val underlyingNetworks = underlyingNetwork?.let { arrayOf(it) }
private val connectivity by lazy { getSystemService<ConnectivityManager>()!! }
@TargetApi(24)
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) = when (intent.action) { override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent) SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
...@@ -154,10 +113,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -154,10 +113,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override fun onRevoke() = stopRunner() override fun onRevoke() = stopRunner()
override suspend fun killProcesses() { override suspend fun killProcesses() {
if (listeningForDefaultNetwork) { active = false
connectivity.unregisterNetworkCallback(defaultNetworkCallback) DefaultNetworkListener.stop(this)
listeningForDefaultNetwork = false
}
worker?.shutdown() worker?.shutdown()
worker = null worker = null
super.killProcesses() super.killProcesses()
...@@ -175,6 +132,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -175,6 +132,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }
override suspend fun resolver(host: String) = DefaultNetworkListener.get().getByName(host)
override suspend fun startProcesses() { override suspend fun startProcesses() {
worker = ProtectWorker().apply { start() } worker = ProtectWorker().apply { start() }
...@@ -230,16 +190,13 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -230,16 +190,13 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
} }
active = true // possible race condition here?
if (Build.VERSION.SDK_INT >= 22) builder.setUnderlyingNetworks(underlyingNetworks)
val conn = builder.establish() ?: throw NullConnectionException() val conn = builder.establish() ?: throw NullConnectionException()
this.conn = conn this.conn = conn
val fd = conn.fd val fd = conn.fd
if (Build.VERSION.SDK_INT >= 24) {
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
}
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath, val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"), "--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"),
"--netif-netmask", "255.255.255.0", "--netif-netmask", "255.255.255.0",
......
/*******************************************************************************
* *
* 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.net
import android.net.LocalSocket
import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.*
import java.io.File
abstract class ConcurrentLocalSocketListener(name: String, socketFile: File) : LocalSocketListener(name, socketFile),
CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext get() = Dispatchers.IO + job + CoroutineExceptionHandler { _, t -> printLog(t) }
override fun accept(socket: LocalSocket) {
launch { super.accept(socket) }
}
suspend fun shutdown() {
job.cancel()
close()
job.join()
}
}
/*******************************************************************************
* *
* 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.net
import android.annotation.TargetApi
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.widget.Toast
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.runBlocking
import java.net.UnknownHostException
object DefaultNetworkListener : CoroutineScope {
override val coroutineContext get() = Dispatchers.Default
private sealed class NetworkMessage {
class Start(val key: Any, val listener: (Network?) -> Unit) : NetworkMessage()
class Get : NetworkMessage() {
val response = CompletableDeferred<Network>()
}
class Stop(val key: Any) : NetworkMessage()
class Put(val network: Network) : NetworkMessage()
class Update(val network: Network) : NetworkMessage()
class Lost(val network: Network) : NetworkMessage()
}
private val networkActor = actor<NetworkMessage> {
val listeners = mutableMapOf<Any, (Network?) -> Unit>()
var network: Network? = null
val pendingRequests = arrayListOf<NetworkMessage.Get>()
for (message in channel) when (message) {
is NetworkMessage.Start -> {
if (listeners.isEmpty()) registerDefaultNetworkListener()
listeners[message.key] = message.listener
if (network != null) message.listener(network)
}
is NetworkMessage.Get -> {
check(listeners.isNotEmpty()) { "Getting network without any listeners is not supported" }
if (network == null) pendingRequests += message else message.response.complete(network)
}
is NetworkMessage.Stop -> {
if (!listeners.isEmpty() && // was not empty
listeners.remove(message.key) != null && listeners.isEmpty()) unregisterDefaultNetworkListener()
}
is NetworkMessage.Put -> {
network = message.network
pendingRequests.forEach { it.response.complete(message.network) }
pendingRequests.clear()
listeners.values.forEach { it(network) }
}
is NetworkMessage.Update -> if (network == message.network) listeners.values.forEach { it(network) }
is NetworkMessage.Lost -> if (network == message.network) {
network = null
listeners.values.forEach { it(null) }
}
}
}
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
} else NetworkMessage.Get().run {
networkActor.send(this)
response.await()
}
suspend fun stop(key: Any) = networkActor.send(NetworkMessage.Stop(key))
// NB: this runs in ConnectivityThread, and this behavior cannot be changed until API 26
private object Callback : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) = runBlocking { networkActor.send(NetworkMessage.Put(network)) }
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
// it's a good idea to refresh capabilities
runBlocking { networkActor.send(NetworkMessage.Update(network)) }
}
override fun onLost(network: Network) = runBlocking { networkActor.send(NetworkMessage.Lost(network)) }
}
private var fallback = false
private val connectivity = app.getSystemService<ConnectivityManager>()!!
private val defaultNetworkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
/**
* 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 fun registerDefaultNetworkListener() {
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
connectivity.registerDefaultNetworkCallback(Callback)
} else try {
fallback = false
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(defaultNetworkRequest, 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 unregisterDefaultNetworkListener() = connectivity.unregisterNetworkCallback(Callback)
}
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.utils package com.github.shadowsocks.net
import android.os.SystemClock import android.os.SystemClock
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
...@@ -28,6 +28,8 @@ import com.github.shadowsocks.Core.app ...@@ -28,6 +28,8 @@ import com.github.shadowsocks.Core.app
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.bg package com.github.shadowsocks.net
import android.net.LocalServerSocket import android.net.LocalServerSocket
import android.net.LocalSocket import android.net.LocalSocket
...@@ -42,7 +42,8 @@ abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name ...@@ -42,7 +42,8 @@ abstract class LocalSocketListener(name: String, socketFile: File) : Thread(name
/** /**
* Inherited class do not need to close input/output streams as they will be closed automatically. * Inherited class do not need to close input/output streams as they will be closed automatically.
*/ */
protected abstract fun accept(socket: LocalSocket) protected open fun accept(socket: LocalSocket) = socket.use { acceptInternal(socket) }
protected abstract fun acceptInternal(socket: LocalSocket)
final override fun run() = localSocket.use { final override fun run() = localSocket.use {
while (running) { while (running) {
try { try {
......
...@@ -18,8 +18,9 @@ ...@@ -18,8 +18,9 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.utils package com.github.shadowsocks.net
import com.github.shadowsocks.utils.parseNumericAddress
import java.net.InetAddress import java.net.InetAddress
import java.util.* import java.util.*
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.utils package com.github.shadowsocks.net
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
......
...@@ -27,7 +27,7 @@ import com.github.shadowsocks.database.PrivateDatabase ...@@ -27,7 +27,7 @@ import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase import com.github.shadowsocks.database.PublicDatabase
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.SocketException import java.net.SocketException
......
...@@ -49,8 +49,8 @@ private val parseNumericAddress by lazy { ...@@ -49,8 +49,8 @@ private val parseNumericAddress by lazy {
* *
* Bug: https://issuetracker.google.com/issues/123456213 * Bug: https://issuetracker.google.com/issues/123456213
*/ */
fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) ?: fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress } ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress }
fun parsePort(str: String?, default: Int, min: Int = 1025): Int { fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
val value = str?.toIntOrNull() ?: default val value = str?.toIntOrNull() ?: default
......
...@@ -28,7 +28,7 @@ import com.github.shadowsocks.bg.BaseService ...@@ -28,7 +28,7 @@ import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.utils.remove import com.github.shadowsocks.utils.remove
import com.takisoft.preferencex.PreferenceFragmentCompat import com.takisoft.preferencex.PreferenceFragmentCompat
......
...@@ -42,7 +42,7 @@ import com.github.shadowsocks.MainActivity ...@@ -42,7 +42,7 @@ import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.Subnet import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.resolveResourceId import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
...@@ -51,6 +51,7 @@ import java.net.IDN ...@@ -51,6 +51,7 @@ import java.net.IDN
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.regex.PatternSyntaxException
class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback { class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback {
companion object { companion object {
...@@ -128,7 +129,15 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -128,7 +129,15 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
private fun validate(template: Int = templateSelector.selectedItemPosition, value: Editable = editText.text) { private fun validate(template: Int = templateSelector.selectedItemPosition, value: Editable = editText.text) {
var message = "" var message = ""
positive.isEnabled = when (Template.values()[template]) { positive.isEnabled = when (Template.values()[template]) {
Template.Generic -> value.isNotEmpty() Template.Generic -> value.toString().run {
try {
if (Subnet.fromString(this) == null) toPattern()
true
} catch (e: PatternSyntaxException) {
message = e.localizedMessage
false
}
}
Template.Domain -> try { Template.Domain -> try {
IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES) IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES)
true true
......
...@@ -33,7 +33,7 @@ import androidx.lifecycle.get ...@@ -33,7 +33,7 @@ import androidx.lifecycle.get
import com.github.shadowsocks.MainActivity import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R import com.github.shadowsocks.R
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.HttpsTest import com.github.shadowsocks.net.HttpsTest
import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomappbar.BottomAppBar
class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
......
...@@ -50,6 +50,8 @@ import com.github.shadowsocks.bg.BaseService ...@@ -50,6 +50,8 @@ import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.Executable import com.github.shadowsocks.bg.Executable
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.* import com.github.shadowsocks.utils.*
......
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