Commit 2eb9adb3 authored by Max Lv's avatar Max Lv

Integrate DNS relay

parent be4d0367
...@@ -5,7 +5,7 @@ apply plugin: 'com.github.ben-manes.versions' ...@@ -5,7 +5,7 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript { buildscript {
ext { ext {
javaVersion = JavaVersion.VERSION_1_8 javaVersion = JavaVersion.VERSION_1_8
kotlinVersion = '1.3.61' kotlinVersion = '1.3.70'
minSdkVersion = 21 minSdkVersion = 21
sdkVersion = 29 sdkVersion = 29
compileSdkVersion = 29 compileSdkVersion = 29
...@@ -32,9 +32,9 @@ buildscript { ...@@ -32,9 +32,9 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.0-beta01' classpath 'com.android.tools.build:gradle:4.0.0-beta02'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.28.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.28.0'
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.9.0' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.9.0'
classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3' classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3'
......
...@@ -38,22 +38,9 @@ object LocalDnsService { ...@@ -38,22 +38,9 @@ object LocalDnsService {
interface Interface : BaseService.Interface { interface Interface : BaseService.Interface {
override suspend fun startProcesses(hosts: HostsFile) { override suspend fun startProcesses(hosts: HostsFile) {
super.startProcesses(hosts) super.startProcesses(hosts)
val profile = data.proxy!!.profile LocalDnsServer(this::resolver, hosts).also {
val dns = try {
URI("dns://${profile.remoteDns}")
} catch (e: URISyntaxException) {
throw BaseService.ExpectedExceptionWrapper(e)
}
LocalDnsServer(this::resolver,
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress,
hosts,
!profile.udpdns,
if (profile.route == Acl.ALL) null else object {
suspend fun createAcl() = AclMatcher().apply { init(profile.route) }
}::createAcl).also {
servers[this] = it servers[this] = it
}.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns)) }.start(InetSocketAddress(DataStore.listenAddress, 8153))
} }
override fun killProcesses(scope: CoroutineScope) { override fun killProcesses(scope: CoroutineScope) {
......
...@@ -120,9 +120,18 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -120,9 +120,18 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
config.put("local_port", DataStore.portProxy) config.put("local_port", DataStore.portProxy)
configFile.writeText(config.toString()) configFile.writeText(config.toString())
val dns = try {
URI("dns://${profile.remoteDns}")
} catch (e: URISyntaxException) {
throw BaseService.ExpectedExceptionWrapper(e)
}
val cmd = arrayListOf( val cmd = arrayListOf(
File((service as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath, File((service as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath,
"--stat-path", stat.absolutePath, "--stat-path", stat.absolutePath,
"--dns-relay", "127.0.0.1:${DataStore.portLocalDns}",
"--remote-dns", "${dns.host}:${if (dns.port < 0) 53 else dns.port}",
"--local-dns", "127.0.0.1:8153",
"-c", configFile.absolutePath) "-c", configFile.absolutePath)
if (service.isVpnService) cmd += arrayListOf("--vpn") if (service.isVpnService) cmd += arrayListOf("--vpn")
if (extraFlag != null) cmd.add(extraFlag) if (extraFlag != null) cmd.add(extraFlag)
......
...@@ -44,14 +44,7 @@ import java.nio.channels.SocketChannel ...@@ -44,14 +44,7 @@ import java.nio.channels.SocketChannel
* https://github.com/shadowsocks/overture/tree/874f22613c334a3b78e40155a55479b7b69fee04 * https://github.com/shadowsocks/overture/tree/874f22613c334a3b78e40155a55479b7b69fee04
*/ */
class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAddress>, class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAddress>,
private val remoteDns: Socks5Endpoint, private val hosts: HostsFile) : CoroutineScope {
private val proxy: SocketAddress,
private val hosts: HostsFile,
/**
* Forward UDP queries to TCP.
*/
private val tcp: Boolean = false,
aclSpawn: (suspend () -> AclMatcher)? = null) : CoroutineScope {
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
...@@ -62,6 +55,10 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -62,6 +55,10 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
private const val TTL = 120L private const val TTL = 120L
private const val UDP_PACKET_SIZE = 512 private const val UDP_PACKET_SIZE = 512
fun emptyDnsResponse() = Message().apply {
header.setFlag(Flags.QR.toInt()) // this is a response
}
fun prepareDnsResponse(request: Message) = Message(request.header.id).apply { fun prepareDnsResponse(request: Message) = Message(request.header.id).apply {
header.setFlag(Flags.QR.toInt()) // this is a response header.setFlag(Flags.QR.toInt()) // this is a response
if (request.header.getFlag(Flags.RD.toInt())) header.setFlag(Flags.RD.toInt()) if (request.header.getFlag(Flags.RD.toInt())) header.setFlag(Flags.RD.toInt())
...@@ -83,7 +80,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -83,7 +80,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t -> override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t ->
if (t is IOException) Crashlytics.log(Log.WARN, TAG, t.message) else printLog(t) if (t is IOException) Crashlytics.log(Log.WARN, TAG, t.message) else printLog(t)
} }
private val acl = aclSpawn?.let { async { it() } }
suspend fun start(listen: SocketAddress) = DatagramChannel.open().run { suspend fun start(listen: SocketAddress) = DatagramChannel.open().run {
configureBlocking(false) configureBlocking(false)
...@@ -110,95 +106,50 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -110,95 +106,50 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
Message(packet) Message(packet)
} catch (e: IOException) { // we cannot parse the message, do not attempt to handle it at all } catch (e: IOException) { // we cannot parse the message, do not attempt to handle it at all
Crashlytics.log(Log.WARN, TAG, e.message) Crashlytics.log(Log.WARN, TAG, e.message)
return forward(packet) return ByteBuffer.wrap(emptyDnsResponse().apply {
header.rcode = Rcode.SERVFAIL
}.toWire())
} }
return supervisorScope {
val remote = async { withTimeout(TIMEOUT) { forward(packet) } }
try {
if (request.header.opcode != Opcode.QUERY) return@supervisorScope remote.await()
val question = request.question val question = request.question
val isIpv6 = when (question?.type) { val isIpv6 = when (question?.type) {
Type.A -> false Type.A -> false
Type.AAAA -> true Type.AAAA -> true
else -> return@supervisorScope remote.await() else -> return ByteBuffer.wrap(prepareDnsResponse(request).apply {
header.rcode = Rcode.SERVFAIL
}.toWire())
} }
val host = question.name.canonicalize().toString(true) val host = question.name.canonicalize().toString(true)
val hostsResults = hosts.resolve(host) val hostsResults = hosts.resolve(host)
if (hostsResults.isNotEmpty()) { if (hostsResults.isNotEmpty()) {
remote.cancel() return ByteBuffer.wrap(cookDnsResponse(request, hostsResults.run {
return@supervisorScope ByteBuffer.wrap(cookDnsResponse(request, hostsResults.run {
if (isIpv6) filterIsInstance<Inet6Address>() else filterIsInstance<Inet4Address>() if (isIpv6) filterIsInstance<Inet6Address>() else filterIsInstance<Inet4Address>()
})) }))
} }
val acl = acl?.await() ?: return@supervisorScope remote.await()
val useLocal = when (acl.shouldBypass(host)) { Log.d("dns", "host: " + 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 (e: java.lang.Exception) {
Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving")
return@supervisorScope remote.await()
} catch (_: UnknownHostException) {
return@supervisorScope remote.await()
}
if (isIpv6) {
val filtered = localResults.filterIsInstance<Inet6Address>()
if (useLocal) return@supervisorScope ByteBuffer.wrap(cookDnsResponse(request, filtered))
if (filtered.any { acl.shouldBypassIpv6(it.address) }) {
remote.cancel()
ByteBuffer.wrap(cookDnsResponse(request, filtered))
} else remote.await()
} else {
val filtered = localResults.filterIsInstance<Inet4Address>()
if (useLocal) return@supervisorScope ByteBuffer.wrap(cookDnsResponse(request, filtered))
if (filtered.any { acl.shouldBypassIpv4(it.address) }) {
remote.cancel()
ByteBuffer.wrap(cookDnsResponse(request, filtered))
} else remote.await()
}
} catch (e: Exception) {
remote.cancel()
when (e) { when (e) {
is TimeoutCancellationException -> Crashlytics.log(Log.WARN, TAG, "Remote resolving timed out") is TimeoutCancellationException -> Crashlytics.log(Log.WARN, TAG, "Remote resolving timed out")
is CancellationException -> { } // ignore is CancellationException -> { } // ignore
is IOException -> Crashlytics.log(Log.WARN, TAG, e.message) is IOException -> Crashlytics.log(Log.WARN, TAG, e.message)
else -> printLog(e) else -> printLog(e)
} }
ByteBuffer.wrap(prepareDnsResponse(request).apply { return ByteBuffer.wrap(prepareDnsResponse(request).apply {
header.rcode = Rcode.SERVFAIL header.rcode = Rcode.SERVFAIL
}.toWire()) }.toWire())
} }
}
}
private suspend fun forward(packet: ByteBuffer): ByteBuffer { val filtered = if (isIpv6) {
packet.position(0) // the packet might have been parsed, reset to beginning localResults.filterIsInstance<Inet6Address>()
return if (tcp) SocketChannel.open().use { channel -> } else {
channel.configureBlocking(false) localResults.filterIsInstance<Inet4Address>()
channel.connect(proxy)
val wrapped = remoteDns.tcpWrap(packet)
while (!channel.finishConnect()) monitor.wait(channel, SelectionKey.OP_CONNECT)
while (channel.write(wrapped) >= 0 && wrapped.hasRemaining()) monitor.wait(channel, SelectionKey.OP_WRITE)
val result = remoteDns.tcpReceiveBuffer(UDP_PACKET_SIZE)
remoteDns.tcpUnwrap(result, channel::read) { monitor.wait(channel, SelectionKey.OP_READ) }
result
} else DatagramChannel.open().use { channel ->
channel.configureBlocking(false)
monitor.wait(channel, SelectionKey.OP_WRITE)
check(channel.send(remoteDns.udpWrap(packet), proxy) > 0)
val result = remoteDns.udpReceiveBuffer(UDP_PACKET_SIZE)
while (isActive) {
monitor.wait(channel, SelectionKey.OP_READ)
if (channel.receive(result) == proxy) break
result.clear()
}
result.flip()
remoteDns.udpUnwrap(result)
result
} }
return ByteBuffer.wrap(cookDnsResponse(request, filtered))
} }
fun shutdown(scope: CoroutineScope) { fun shutdown(scope: CoroutineScope) {
...@@ -206,7 +157,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -206,7 +157,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
monitor.close(scope) monitor.close(scope)
scope.launch { scope.launch {
this@LocalDnsServer.coroutineContext[Job]!!.join() this@LocalDnsServer.coroutineContext[Job]!!.join()
acl?.also { it.await().close() }
} }
} }
} }
Subproject commit 8b836065d26f1fc6e3f51fc47e4622ac61017ea3 Subproject commit 92fef6ed1204737f2a94fd6efefa8aeadbd4d648
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