Commit 55b960fe authored by Mygod's avatar Mygod

Respect ACL files when doing local DNS

parent 9f26ec8f
/*******************************************************************************
* *
* 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.acl
import com.github.shadowsocks.utils.asIterable
import java.net.Inet4Address
import java.net.Inet6Address
class AclMatcher(acl: Acl) {
private val subnetsIpv4 = acl.subnets.asIterable().filter { it.address is Inet4Address }
private val subnetsIpv6 = acl.subnets.asIterable().filter { it.address is Inet6Address }
private val bypassDomains = acl.bypassHostnames.asIterable().map { it.toRegex() }
private val proxyDomains = acl.proxyHostnames.asIterable().map { it.toRegex() }
private val bypass = acl.bypass
fun shouldBypass(ip: Inet4Address) = bypass xor subnetsIpv4.any { it.matches(ip) }
fun shouldBypass(ip: Inet6Address) = bypass xor subnetsIpv6.any { it.matches(ip) }
fun shouldBypass(host: String): Boolean? {
if (bypassDomains.any { it.matches(host) }) return true
if (proxyDomains.any { it.matches(host) }) return false
return null
}
}
...@@ -234,7 +234,7 @@ object BaseService { ...@@ -234,7 +234,7 @@ object BaseService {
fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> = cmd fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> = cmd
suspend fun startProcesses(hosts: HostsFile) { suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>() val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()
?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir ?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir
val udpFallback = data.udpFallback val udpFallback = data.udpFallback
...@@ -349,19 +349,21 @@ object BaseService { ...@@ -349,19 +349,21 @@ object BaseService {
val hosts = HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "") val hosts = HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "")
proxy.init(this@Interface, hosts) proxy.init(this@Interface, hosts)
data.udpFallback?.init(this@Interface, hosts) data.udpFallback?.init(this@Interface, hosts)
if (profile.route == Acl.CUSTOM_RULES) try { val acl = if (profile.route == Acl.CUSTOM_RULES) try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Acl.save(Acl.CUSTOM_RULES, Acl.customRules.flatten(10, this@Interface::openConnection)) Acl.customRules.flatten(10, this@Interface::openConnection).also {
} Acl.save(Acl.CUSTOM_RULES, it)
}
}.let { lazy { it } }
} catch (e: IOException) { } catch (e: IOException) {
throw ExpectedExceptionWrapper(e) throw ExpectedExceptionWrapper(e)
} } else lazy { if (profile.route == Acl.ALL) null else Acl().fromId(profile.route) }
data.processes = GuardedProcessPool { data.processes = GuardedProcessPool {
printLog(it) printLog(it)
stopRunner(false, it.readableMessage) stopRunner(false, it.readableMessage)
} }
startProcesses(hosts) startProcesses(hosts, acl)
proxy.scheduleUpdate() proxy.scheduleUpdate()
data.udpFallback?.scheduleUpdate() data.udpFallback?.scheduleUpdate()
......
...@@ -20,13 +20,11 @@ ...@@ -20,13 +20,11 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
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.acl.AclMatcher
import com.github.shadowsocks.net.HostsFile import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.net.LocalDnsServer import com.github.shadowsocks.net.LocalDnsServer
import com.github.shadowsocks.net.Socks5Endpoint import com.github.shadowsocks.net.Socks5Endpoint
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import java.net.InetSocketAddress import java.net.InetSocketAddress
...@@ -35,24 +33,11 @@ import java.net.URISyntaxException ...@@ -35,24 +33,11 @@ import java.net.URISyntaxException
import java.util.* import java.util.*
object LocalDnsService { object LocalDnsService {
private val googleApisTester =
"(^|\\.)googleapis(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}\$".toRegex()
private val chinaIpv4List by lazy {
app.resources.openRawResource(R.raw.zone_cn).bufferedReader().lineSequence().map {
Subnet.fromString(it, 4)
}.filterNotNull().toList()
}
private val chinaIpv6List by lazy {
app.resources.openRawResource(R.raw.zone_cn_ipv6).bufferedReader().lineSequence().map {
Subnet.fromString(it, 16)
}.filterNotNull().toList()
}
private val servers = WeakHashMap<Interface, LocalDnsServer>() private val servers = WeakHashMap<Interface, LocalDnsServer>()
interface Interface : BaseService.Interface { interface Interface : BaseService.Interface {
override suspend fun startProcesses(hosts: HostsFile) { override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
super.startProcesses(hosts) super.startProcesses(hosts, acl)
val profile = data.proxy!!.profile val profile = data.proxy!!.profile
val dns = try { val dns = try {
URI("dns://${profile.remoteDns}") URI("dns://${profile.remoteDns}")
...@@ -62,18 +47,11 @@ object LocalDnsService { ...@@ -62,18 +47,11 @@ object LocalDnsService {
LocalDnsServer(this::resolver, LocalDnsServer(this::resolver,
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port), Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress, DataStore.proxyAddress,
hosts).apply { hosts,
tcp = !profile.udpdns !profile.udpdns,
when (profile.route) { acl.value?.let { AclMatcher(it) }).also {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> { servers[this] = it
remoteDomainMatcher = googleApisTester }.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
localIpv4Matcher = chinaIpv4List
localIpv6Matcher = chinaIpv6List
}
Acl.CHINALIST -> { }
else -> forwardOnly = true
}
}.also { servers[this] = it }.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
} }
override fun killProcesses(scope: CoroutineScope) { override fun killProcesses(scope: CoroutineScope) {
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg ...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.net.HostsFile import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import java.io.File import java.io.File
...@@ -57,9 +58,9 @@ redsocks { ...@@ -57,9 +58,9 @@ redsocks {
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf")) File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf"))
} }
override suspend fun startProcesses(hosts: HostsFile) { override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
startRedsocksDaemon() startRedsocksDaemon()
super.startProcesses(hosts) super.startProcesses(hosts, acl)
} }
override fun onDestroy() { override fun onDestroy() {
......
...@@ -144,9 +144,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -144,9 +144,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override suspend fun resolver(host: String) = DnsResolverCompat.resolve(DefaultNetworkListener.get(), 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(hosts: HostsFile) { override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
worker = ProtectWorker().apply { start() } worker = ProtectWorker().apply { start() }
super.startProcesses(hosts) super.startProcesses(hosts, acl)
sendFd(startVpn()) sendFd(startVpn())
} }
......
...@@ -22,6 +22,7 @@ package com.github.shadowsocks.net ...@@ -22,6 +22,7 @@ package com.github.shadowsocks.net
import android.util.Log import android.util.Log
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.AclMatcher
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
import kotlinx.coroutines.* import kotlinx.coroutines.*
...@@ -45,19 +46,12 @@ import java.nio.channels.SocketChannel ...@@ -45,19 +46,12 @@ import java.nio.channels.SocketChannel
class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAddress>, class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAddress>,
private val remoteDns: Socks5Endpoint, private val remoteDns: Socks5Endpoint,
private val proxy: SocketAddress, private val proxy: SocketAddress,
private val hosts: HostsFile) : CoroutineScope { private val hosts: HostsFile,
/** /**
* Forward all requests to remote and ignore localResolver. * Forward UDP queries to TCP.
*/ */
var forwardOnly = false private val tcp: Boolean = false,
/** private val acl: AclMatcher? = null) : CoroutineScope {
* Forward UDP queries to TCP.
*/
var tcp = true
var remoteDomainMatcher: Regex? = null
var localIpv4Matcher: List<Subnet> = emptyList()
var localIpv6Matcher: List<Subnet> = emptyList()
companion object { companion object {
private const val TAG = "LocalDnsServer" private const val TAG = "LocalDnsServer"
private const val TIMEOUT = 10_000L private const val TIMEOUT = 10_000L
...@@ -134,8 +128,12 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -134,8 +128,12 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
remote.cancel() remote.cancel()
return@supervisorScope cookDnsResponse(request, hostsResults) return@supervisorScope cookDnsResponse(request, hostsResults)
} }
if (forwardOnly) return@supervisorScope remote.await() if (acl == null) return@supervisorScope remote.await()
if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await() val useLocal = when (acl.shouldBypass(host)) {
true -> true.also { remote.cancel() }
false -> return@supervisorScope remote.await()
null -> false
}
val localResults = try { val localResults = try {
withTimeout(TIMEOUT) { localResolver(host) } withTimeout(TIMEOUT) { localResolver(host) }
} catch (_: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
...@@ -144,12 +142,21 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -144,12 +142,21 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
return@supervisorScope remote.await() return@supervisorScope remote.await()
} }
if (localResults.isEmpty()) return@supervisorScope remote.await() if (isIpv6) {
val matcher = if (isIpv6) localIpv6Matcher else localIpv4Matcher val filtered = localResults.filterIsInstance<Inet6Address>()
if (matcher.isEmpty() || matcher.any { subnet -> localResults.any(subnet::matches) }) { if (useLocal) return@supervisorScope cookDnsResponse(request, filtered)
remote.cancel() if (filtered.any { acl.shouldBypass(it) }) {
cookDnsResponse(request, localResults.asIterable()) remote.cancel()
} else remote.await() cookDnsResponse(request, filtered)
} else remote.await()
} else {
val filtered = localResults.filterIsInstance<Inet4Address>()
if (useLocal) return@supervisorScope cookDnsResponse(request, filtered)
if (filtered.any { acl.shouldBypass(it) }) {
remote.cancel()
cookDnsResponse(request, filtered)
} else remote.await()
}
} catch (e: Exception) { } catch (e: Exception) {
remote.cancel() remote.cancel()
when (e) { when (e) {
......
This diff is collapsed.
This diff is collapsed.
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