Skip to content

Commit

Permalink
Switch to the local UDP DNS resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
madeye committed Dec 24, 2020
1 parent 016b5b3 commit 43edaba
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ object BaseService {
File(Core.deviceStorage.noBackupFilesDir, "stat_udp"),
File(configRoot, CONFIG_FILE_UDP),
"-u", false)
data.localDns = LocalDnsWorker(this::rawResolver).apply { start() }
data.localDns = LocalDnsWorker(this::rawResolver, DataStore.portLocalDns + 1).apply { start() }
}

fun startRunner() {
Expand Down
74 changes: 35 additions & 39 deletions core/src/main/java/com/github/shadowsocks/bg/LocalDnsWorker.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.github.shadowsocks.bg

import android.net.LocalSocket
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.ConcurrentUdpSocketListener
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.utils.readableMessage
import kotlinx.coroutines.CancellationException
Expand All @@ -12,47 +10,45 @@ import kotlinx.coroutines.launch
import org.xbill.DNS.Message
import org.xbill.DNS.Rcode
import timber.log.Timber
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.IOException
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel

class LocalDnsWorker(private val resolver: suspend (ByteArray) -> ByteArray) : ConcurrentLocalSocketListener(
"LocalDnsThread", File(Core.deviceStorage.noBackupFilesDir, "local_dns_path")), CoroutineScope {
override fun acceptInternal(socket: LocalSocket) = error("big no no")
override fun accept(socket: LocalSocket) {
class LocalDnsWorker(private val resolver: suspend (ByteArray) -> ByteArray, port: Int) : ConcurrentUdpSocketListener(
"LocalDnsThread", port), CoroutineScope {

override fun handle(channel: DatagramChannel, sender: SocketAddress, query: ByteBuffer) {
launch {
socket.use {
val input = DataInputStream(socket.inputStream)
val query = try {
ByteArray(input.readUnsignedShort()).also { input.read(it) }
} catch (e: IOException) { // connection early close possibly due to resolving timeout
return@use Timber.d(e)
query.flip()
val data = ByteArray(query.remaining())
query.get(data)
try {
resolver(data)
} catch (e: Exception) {
when (e) {
is TimeoutCancellationException -> Timber.w("Resolving timed out")
is CancellationException -> {
} // ignore
is IOException -> Timber.d(e)
else -> Timber.w(e)
}
try {
DnsResolverCompat.prepareDnsResponse(Message(data)).apply {
header.rcode = Rcode.SERVFAIL
}.toWire()
} catch (_: IOException) {
byteArrayOf() // return empty if cannot parse packet
}
}?.let { r ->
try {
resolver(query)
} catch (e: Exception) {
when (e) {
is TimeoutCancellationException -> Timber.w("Resolving timed out")
is CancellationException -> { } // ignore
is IOException -> Timber.d(e)
else -> Timber.w(e)
}
try {
DnsResolverCompat.prepareDnsResponse(Message(query)).apply {
header.rcode = Rcode.SERVFAIL
}.toWire()
} catch (_: IOException) {
byteArrayOf() // return empty if cannot parse packet
}
}?.let { response ->
try {
val output = DataOutputStream(socket.outputStream)
output.writeShort(response.size)
output.write(response)
} catch (e: IOException) {
Timber.d(e.readableMessage)
}
val response = ByteBuffer.allocate(1024)
response.clear()
response.put(r)
response.flip()
channel.send(response, sender)
} catch (e: IOException) {
Timber.d(e.readableMessage)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}.let { dns ->
cmd += arrayListOf(
"--dns-addr", "${DataStore.listenAddress}:${DataStore.portLocalDns}",
"--local-dns-addr", "127.0.0.1:${DataStore.portLocalDns + 1}",
"--remote-dns-addr", "${dns.host ?: "0.0.0.0"}:${if (dns.port < 0) 53 else dns.port}")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* *
* 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 kotlinx.coroutines.*
import timber.log.Timber

abstract class ConcurrentUdpSocketListener(name: String, port: Int) : UdpSocketListener(name, port),
CoroutineScope {
override val coroutineContext = Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, t -> Timber.w(t) }

override fun shutdown(scope: CoroutineScope) {
running = false
cancel()
super.shutdown(scope)
coroutineContext[Job]!!.also { job -> scope.launch { job.join() } }
}
}
72 changes: 72 additions & 0 deletions core/src/main/java/com/github/shadowsocks/net/UdpSocketListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 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.SuppressLint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.IOException
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel

abstract class UdpSocketListener(name: String, val port: Int) : Thread(name) {

private val udpChannel = DatagramChannel.open()
private val closeChannel = Channel<Unit>(1)

@Volatile
protected var running = true

/**
* Inherited class do not need to close input/output streams as they will be closed automatically.
*/
protected abstract fun handle(channel: DatagramChannel, sender: SocketAddress, query: ByteBuffer)

final override fun run() {
udpChannel.socket().bind(InetSocketAddress(port))
udpChannel.configureBlocking(true)
udpChannel.use {
while (running) {
try {
val query = ByteBuffer.allocate(1024)
query.clear()
udpChannel.receive(query)?.let { handle(udpChannel, it, query) }
} catch (e: IOException) {
if (running) Timber.w(e)
continue
}
}
}
closeChannel.sendBlocking(Unit)
}

@SuppressLint("NewApi")
open fun shutdown(scope: CoroutineScope) {
running = false
udpChannel.close()
scope.launch { closeChannel.receive() }
}
}

0 comments on commit 43edaba

Please sign in to comment.