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'
buildscript {
ext {
javaVersion = JavaVersion.VERSION_1_8
kotlinVersion = '1.3.61'
kotlinVersion = '1.3.70'
minSdkVersion = 21
sdkVersion = 29
compileSdkVersion = 29
......@@ -32,9 +32,9 @@ buildscript {
}
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.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.vanniktech:gradle-maven-publish-plugin:0.9.0'
classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3'
......
......@@ -38,22 +38,9 @@ object LocalDnsService {
interface Interface : BaseService.Interface {
override suspend fun startProcesses(hosts: HostsFile) {
super.startProcesses(hosts)
val profile = data.proxy!!.profile
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 {
LocalDnsServer(this::resolver, hosts).also {
servers[this] = it
}.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
}.start(InetSocketAddress(DataStore.listenAddress, 8153))
}
override fun killProcesses(scope: CoroutineScope) {
......
......@@ -120,9 +120,18 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
config.put("local_port", DataStore.portProxy)
configFile.writeText(config.toString())
val dns = try {
URI("dns://${profile.remoteDns}")
} catch (e: URISyntaxException) {
throw BaseService.ExpectedExceptionWrapper(e)
}
val cmd = arrayListOf(
File((service as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).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)
if (service.isVpnService) cmd += arrayListOf("--vpn")
if (extraFlag != null) cmd.add(extraFlag)
......
......@@ -44,14 +44,7 @@ import java.nio.channels.SocketChannel
* https://github.com/shadowsocks/overture/tree/874f22613c334a3b78e40155a55479b7b69fee04
*/
class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAddress>,
private val remoteDns: Socks5Endpoint,
private val proxy: SocketAddress,
private val hosts: HostsFile,
/**
* Forward UDP queries to TCP.
*/
private val tcp: Boolean = false,
aclSpawn: (suspend () -> AclMatcher)? = null) : CoroutineScope {
private val hosts: HostsFile) : CoroutineScope {
companion object {
private const val TAG = "LocalDnsServer"
private const val TIMEOUT = 10_000L
......@@ -62,6 +55,10 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
private const val TTL = 120L
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 {
header.setFlag(Flags.QR.toInt()) // this is a response
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
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, 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 {
configureBlocking(false)
......@@ -110,95 +106,50 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
Message(packet)
} catch (e: IOException) { // we cannot parse the message, do not attempt to handle it at all
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 isIpv6 = when (question?.type) {
Type.A -> false
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 hostsResults = hosts.resolve(host)
if (hostsResults.isNotEmpty()) {
remote.cancel()
return@supervisorScope ByteBuffer.wrap(cookDnsResponse(request, hostsResults.run {
return ByteBuffer.wrap(cookDnsResponse(request, hostsResults.run {
if (isIpv6) filterIsInstance<Inet6Address>() else filterIsInstance<Inet4Address>()
}))
}
val acl = acl?.await() ?: return@supervisorScope remote.await()
val useLocal = when (acl.shouldBypass(host)) {
true -> true.also { remote.cancel() }
false -> return@supervisorScope remote.await()
null -> false
}
Log.d("dns", "host: " + host)
val localResults = try {
withTimeout(TIMEOUT) { localResolver(host) }
} catch (_: TimeoutCancellationException) {
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()
} catch (e: java.lang.Exception) {
when (e) {
is TimeoutCancellationException -> Crashlytics.log(Log.WARN, TAG, "Remote resolving timed out")
is CancellationException -> { } // ignore
is IOException -> Crashlytics.log(Log.WARN, TAG, e.message)
else -> printLog(e)
}
ByteBuffer.wrap(prepareDnsResponse(request).apply {
return ByteBuffer.wrap(prepareDnsResponse(request).apply {
header.rcode = Rcode.SERVFAIL
}.toWire())
}
}
}
private suspend fun forward(packet: ByteBuffer): ByteBuffer {
packet.position(0) // the packet might have been parsed, reset to beginning
return if (tcp) SocketChannel.open().use { channel ->
channel.configureBlocking(false)
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
val filtered = if (isIpv6) {
localResults.filterIsInstance<Inet6Address>()
} else {
localResults.filterIsInstance<Inet4Address>()
}
return ByteBuffer.wrap(cookDnsResponse(request, filtered))
}
fun shutdown(scope: CoroutineScope) {
......@@ -206,7 +157,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
monitor.close(scope)
scope.launch {
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