From 707c4e40853554b0f675413df848a9c86fb2e13b Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 24 Dec 2020 15:07:46 +0800 Subject: [PATCH 1/4] Switch to the local UDP DNS resolver --- .../com/github/shadowsocks/bg/BaseService.kt | 2 +- .../github/shadowsocks/bg/LocalDnsWorker.kt | 74 +++++++++---------- .../github/shadowsocks/bg/ProxyInstance.kt | 2 +- .../net/ConcurrentUdpSocketListener.kt | 36 +++++++++ .../shadowsocks/net/UdpSocketListener.kt | 72 ++++++++++++++++++ 5 files changed, 145 insertions(+), 41 deletions(-) create mode 100644 core/src/main/java/com/github/shadowsocks/net/ConcurrentUdpSocketListener.kt create mode 100644 core/src/main/java/com/github/shadowsocks/net/UdpSocketListener.kt diff --git a/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt b/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt index 422d8f424f..3fb103489d 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt @@ -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() { diff --git a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsWorker.kt b/core/src/main/java/com/github/shadowsocks/bg/LocalDnsWorker.kt index 1c2b87719d..89dc09afab 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsWorker.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/LocalDnsWorker.kt @@ -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 @@ -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) } } } diff --git a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt index 3b936f2914..6038e466fe 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt @@ -91,7 +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", "local_dns_path", + "--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}") } diff --git a/core/src/main/java/com/github/shadowsocks/net/ConcurrentUdpSocketListener.kt b/core/src/main/java/com/github/shadowsocks/net/ConcurrentUdpSocketListener.kt new file mode 100644 index 0000000000..2daf919a18 --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/net/ConcurrentUdpSocketListener.kt @@ -0,0 +1,36 @@ +/******************************************************************************* + * * + * Copyright (C) 2019 by Max Lv * + * Copyright (C) 2019 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +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() } } + } +} diff --git a/core/src/main/java/com/github/shadowsocks/net/UdpSocketListener.kt b/core/src/main/java/com/github/shadowsocks/net/UdpSocketListener.kt new file mode 100644 index 0000000000..21ffb45937 --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/net/UdpSocketListener.kt @@ -0,0 +1,72 @@ +/******************************************************************************* + * * + * Copyright (C) 2017 by Max Lv * + * Copyright (C) 2017 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +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(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() } + } +} From f6a8ea9b00e144dd3a53d8ae82b1dc33b30767ef Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 28 Dec 2020 12:16:11 +0800 Subject: [PATCH 2/4] Update shadowsocks-rust --- .circleci/config.yml | 4 ++-- core/src/main/rust/shadowsocks-rust | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d5e40256f..ac5530ad2a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,8 +7,8 @@ jobs: environment: GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process" steps: - - run: rustup toolchain install nightly-2020-12-20 - - run: rustup override set nightly-2020-12-20 + - run: rustup toolchain install nightly-2020-12-17 + - run: rustup override set nightly-2020-12-17 - run: rustup target install armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android - checkout - run: git submodule update --init --recursive diff --git a/core/src/main/rust/shadowsocks-rust b/core/src/main/rust/shadowsocks-rust index 3a1054acc5..75c28313ea 160000 --- a/core/src/main/rust/shadowsocks-rust +++ b/core/src/main/rust/shadowsocks-rust @@ -1 +1 @@ -Subproject commit 3a1054acc54cfc23a1cd232aa0a45b66af4bd612 +Subproject commit 75c28313ea3396c7be691640009058a7e0fb8388 From 45733069240c41a2b6ac4aa2369ee2996de80139 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 30 Dec 2020 16:52:14 +0800 Subject: [PATCH 3/4] Revert the rustup commands --- .circleci/config.yml | 5 ++--- README.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac5530ad2a..b274fdb624 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,11 +7,10 @@ jobs: environment: GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process" steps: - - run: rustup toolchain install nightly-2020-12-17 - - run: rustup override set nightly-2020-12-17 - - run: rustup target install armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android - checkout - run: git submodule update --init --recursive + - run: rustup update + - run: cd core/src/main/rust/shadowsocks-rust && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android - restore_cache: key: jars-{{ checksum "build.gradle.kts" }} - run: diff --git a/README.md b/README.md index 12e51f795a..a3ea7e628b 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,8 @@ for Android TV ([beta](https://play.google.com/apps/testing/com.github.shadowsoc * Rust with Android targets installed ```bash - $ rustup toolchain install nightly-2020-12-20 - $ rustup override set nightly-2020-12-20 - $ rustup target install armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android + $ cd core/src/main/rust/shadowsocks-rust + $ rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android ``` ### BUILD From 3a89c52ca3d52df894dcde442bdc92077d9ee403 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 31 Dec 2020 08:30:55 +0800 Subject: [PATCH 4/4] Fix #2642 --- core/src/main/res/values/arrays.xml | 2 -- core/src/main/rust/shadowsocks-rust | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 5aa114c6e7..f50e97843c 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -16,7 +16,6 @@ AES-128-GCM AES-256-GCM CHACHA20-IETF-POLY1305 - XCHACHA20-IETF-POLY1305 @@ -35,7 +34,6 @@ aes-128-gcm aes-256-gcm chacha20-ietf-poly1305 - xchacha20-ietf-poly1305 diff --git a/core/src/main/rust/shadowsocks-rust b/core/src/main/rust/shadowsocks-rust index 75c28313ea..e048bc2f34 160000 --- a/core/src/main/rust/shadowsocks-rust +++ b/core/src/main/rust/shadowsocks-rust @@ -1 +1 @@ -Subproject commit 75c28313ea3396c7be691640009058a7e0fb8388 +Subproject commit e048bc2f343bbed36ed6dea0151d34ceecfce8ca