diff --git a/README.md b/README.md
index 72f1b1fa..ddfe32c7 100644
--- a/README.md
+++ b/README.md
@@ -83,8 +83,6 @@ GPLv3
kotlin: APL 2.0
material: APL 2.0
gson: APL 2.0
- dnsjava: BSD
- jsocks: LGPL 2.1
preferencex-simplemenu: APL 2.0
android-plugin-api-for-locale: APL 2.0
qrgen: APL 2.0
diff --git a/core/build.gradle b/core/build.gradle
index d9663061..14b98c8a 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -78,9 +78,7 @@ dependencies {
api "androidx.work:work-runtime-ktx:2.7.1"
api "androidx.work:work-multiprocess:2.7.1"
api 'com.google.code.gson:gson:2.9.0'
- api 'dnsjava:dnsjava:3.5.0'
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
- implementation 'com.github.ssrlive:jsocks:v1.0.1'
implementation 'androidx.core:core-ktx:1.7.0'
kapt "androidx.room:room-compiler:2.4.2"
}
diff --git a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt b/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
deleted file mode 100644
index d3c4e077..00000000
--- a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*******************************************************************************
- * *
- * 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.bg
-
-import com.github.shadowsocks.acl.Acl
-import com.github.shadowsocks.acl.AclMatcher
-import com.github.shadowsocks.net.HostsFile
-import com.github.shadowsocks.net.LocalDnsServer
-import com.github.shadowsocks.net.Socks5Endpoint
-import com.github.shadowsocks.preference.DataStore
-import kotlinx.coroutines.CoroutineScope
-import java.net.InetSocketAddress
-import java.net.URI
-import java.net.URISyntaxException
-import java.util.WeakHashMap
-
-object LocalDnsService {
- private val servers = WeakHashMap()
-
- 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 {
- servers[this] = it
- }.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
- }
-
- override fun killProcesses(scope: CoroutineScope) {
- servers.remove(this)?.shutdown(scope)
- super.killProcesses(scope)
- }
- }
-}
diff --git a/core/src/main/java/com/github/shadowsocks/bg/SsrVpnService.kt b/core/src/main/java/com/github/shadowsocks/bg/SsrVpnService.kt
index 709cfa30..0624d298 100644
--- a/core/src/main/java/com/github/shadowsocks/bg/SsrVpnService.kt
+++ b/core/src/main/java/com/github/shadowsocks/bg/SsrVpnService.kt
@@ -48,7 +48,7 @@ import java.io.File
import java.io.FileDescriptor
import java.io.IOException
-class SsrVpnService : VpnService(), LocalDnsService.Interface {
+class SsrVpnService : VpnService(), BaseService.Interface {
companion object {
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1"
@@ -84,7 +84,7 @@ class SsrVpnService : VpnService(), LocalDnsService.Interface {
override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super.onBind(intent)
- else -> super.onBind(intent)
+ else -> super.onBind(intent)
}
override fun onRevoke() = stopRunner()
@@ -105,7 +105,9 @@ class SsrVpnService : VpnService(), LocalDnsService.Interface {
if (DataStore.serviceMode == Key.modeVpn) {
if (prepare(this) != null) {
startActivity(Intent(this, VpnRequestActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
- } else return super.onStartCommand(intent, flags, startId)
+ } else {
+ return super.onStartCommand(intent, flags, startId)
+ }
}
stopRunner()
return Service.START_NOT_STICKY
diff --git a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt b/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
deleted file mode 100644
index 9bfc5aa2..00000000
--- a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
+++ /dev/null
@@ -1,213 +0,0 @@
-/*******************************************************************************
- * *
- * 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 android.util.Log
-import com.crashlytics.android.Crashlytics
-import com.github.shadowsocks.acl.AclMatcher
-import com.github.shadowsocks.bg.BaseService
-import com.github.shadowsocks.utils.printLog
-import kotlinx.coroutines.*
-import org.xbill.DNS.*
-import java.io.IOException
-import java.net.*
-import java.nio.ByteBuffer
-import java.nio.channels.DatagramChannel
-import java.nio.channels.SelectionKey
-import java.nio.channels.SocketChannel
-
-/**
- * A simple DNS conditional forwarder.
- *
- * No cache is provided as localResolver may change from time to time. We expect DNS clients to do cache themselves.
- *
- * Based on:
- * https://github.com/bitcoinj/httpseed/blob/809dd7ad9280f4bc98a356c1ffb3d627bf6c7ec5/src/main/kotlin/dns.kt
- * https://github.com/shadowsocks/overture/tree/874f22613c334a3b78e40155a55479b7b69fee04
- */
-class LocalDnsServer(private val localResolver: suspend (String) -> Array,
- 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 {
- companion object {
- private const val TAG = "LocalDnsServer"
- private const val TIMEOUT = 10_000L
- /**
- * TTL returned from localResolver is set to 120. Android API does not provide TTL,
- * so we suppose Android apps should not care about TTL either.
- */
- private const val TTL = 120L
- private const val UDP_PACKET_SIZE = 512
-
- private 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())
- request.question?.also { addRecord(it, Section.QUESTION) }
- }
-
- private fun cookDnsResponse(request: Message, results: Iterable) =
- ByteBuffer.wrap(prepareDnsResponse(request).apply {
- header.setFlag(Flags.RA.toInt()) // recursion available
- for (address in results) addRecord(when (address) {
- is Inet4Address -> ARecord(question.name, DClass.IN, TTL, address)
- is Inet6Address -> AAAARecord(question.name, DClass.IN, TTL, address)
- else -> error("Unsupported address $address")
- }, Section.ANSWER)
- }.toWire())
- }
-
- private val monitor = ChannelMonitor()
-
- 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)
- try {
- socket().bind(listen)
- } catch (e: BindException) {
- throw BaseService.ExpectedExceptionWrapper(e)
- }
- monitor.register(this, SelectionKey.OP_READ) { handlePacket(this) }
- }
-
- private fun handlePacket(channel: DatagramChannel) {
- val buffer = ByteBuffer.allocateDirect(UDP_PACKET_SIZE)
- val source = channel.receive(buffer)!!
- buffer.flip()
- launch {
- val reply = resolve(buffer)
- while (channel.send(reply, source) <= 0) monitor.wait(channel, SelectionKey.OP_WRITE)
- }
- }
-
- private suspend fun resolve(packet: ByteBuffer): ByteBuffer {
- val request = try {
- 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 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()
- }
- val host = question.name.canonicalize().toString(true)
- val hostsResults = hosts.resolve(host)
- if (hostsResults.isNotEmpty()) {
- remote.cancel()
- return@supervisorScope cookDnsResponse(request, hostsResults.run {
- if (isIpv6) filterIsInstance() else filterIsInstance()
- })
- }
- 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
- }
- 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()
- if (useLocal) return@supervisorScope cookDnsResponse(request, filtered)
- if (filtered.any { acl.shouldBypassIpv6(it.address) }) {
- remote.cancel()
- cookDnsResponse(request, filtered)
- } else remote.await()
- } else {
- val filtered = localResults.filterIsInstance()
- if (useLocal) return@supervisorScope cookDnsResponse(request, filtered)
- if (filtered.any { acl.shouldBypassIpv4(it.address) }) {
- remote.cancel()
- cookDnsResponse(request, filtered)
- } else remote.await()
- }
- } catch (e: Exception) {
- remote.cancel()
- 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 {
- 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
- }
- }
-
- fun shutdown(scope: CoroutineScope) {
- cancel()
- monitor.close(scope)
- scope.launch {
- this@LocalDnsServer.coroutineContext[Job]!!.join()
- acl?.also { it.await().close() }
- }
- }
-}
diff --git a/core/src/main/java/com/github/shadowsocks/net/Socks5Endpoint.kt b/core/src/main/java/com/github/shadowsocks/net/Socks5Endpoint.kt
deleted file mode 100644
index f30b98a4..00000000
--- a/core/src/main/java/com/github/shadowsocks/net/Socks5Endpoint.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*******************************************************************************
- * *
- * 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 com.github.shadowsocks.utils.parseNumericAddress
-import net.sourceforge.jsocks.Socks4Message
-import net.sourceforge.jsocks.Socks5Message
-import java.io.EOFException
-import java.io.IOException
-import java.net.Inet4Address
-import java.net.Inet6Address
-import java.nio.ByteBuffer
-import kotlin.math.max
-
-class Socks5Endpoint(host: String, port: Int) {
- private val dest = host.parseNumericAddress().let { numeric ->
- val bytes = numeric?.address ?: host.toByteArray().apply { check(size < 256) { "Hostname too long" } }
- val type = when (numeric) {
- null -> Socks5Message.SOCKS_ATYP_DOMAINNAME
- is Inet4Address -> Socks5Message.SOCKS_ATYP_IPV4
- is Inet6Address -> Socks5Message.SOCKS_ATYP_IPV6
- else -> error("Unsupported address type $numeric")
- }
- ByteBuffer.allocate(bytes.size + (if (numeric == null) 1 else 0) + 3).apply {
- put(type.toByte())
- if (numeric == null) put(bytes.size.toByte())
- put(bytes)
- putShort(port.toShort())
- }
- }.array()
- private val headerReserved = max(3 + 3 + 16, 3 + dest.size)
-
- fun tcpWrap(message: ByteBuffer): ByteBuffer {
- check(message.remaining() < 65536) { "TCP message too large" }
- return ByteBuffer.allocateDirect(8 + dest.size + message.remaining()).apply {
- put(Socks5Message.SOCKS_VERSION.toByte())
- put(1) // nmethods
- put(0) // no authentication required
- // header
- put(Socks5Message.SOCKS_VERSION.toByte())
- put(Socks4Message.REQUEST_CONNECT.toByte())
- put(0) // reserved
- put(dest)
- // data
- putShort(message.remaining().toShort())
- put(message)
- flip()
- }
- }
- fun tcpReceiveBuffer(size: Int) = ByteBuffer.allocateDirect(headerReserved + 4 + size)
- suspend fun tcpUnwrap(buffer: ByteBuffer, reader: (ByteBuffer) -> Int, wait: suspend () -> Unit) {
- suspend fun readBytes(till: Int) {
- if (buffer.position() >= till) return
- while (reader(buffer) >= 0 && buffer.position() < till) wait()
- if (buffer.position() < till) throw EOFException("${buffer.position()} < $till")
- }
- suspend fun read(index: Int): Byte {
- readBytes(index + 1)
- return buffer[index]
- }
- if (read(0) != Socks5Message.SOCKS_VERSION.toByte()) throw IOException("Unsupported SOCKS version ${buffer[0]}")
- if (read(1) != 0.toByte()) throw IOException("Unsupported authentication ${buffer[1]}")
- if (read(2) != Socks5Message.SOCKS_VERSION.toByte()) throw IOException("Unsupported SOCKS version ${buffer[2]}")
- if (read(3) != 0.toByte()) throw IOException("SOCKS5 server returned error ${buffer[3]}")
- val dataOffset = when (val type = read(5)) {
- Socks5Message.SOCKS_ATYP_IPV4.toByte() -> 4
- Socks5Message.SOCKS_ATYP_DOMAINNAME.toByte() -> 1 + read(6)
- Socks5Message.SOCKS_ATYP_IPV6.toByte() -> 16
- else -> throw IOException("Unsupported address type $type")
- } + 8
- readBytes(dataOffset + 2)
- buffer.limit(buffer.position()) // store old position to update mark
- buffer.position(dataOffset)
- val dataLength = buffer.short.toUShort().toInt()
- val end = buffer.position() + dataLength
- if (end > buffer.capacity()) throw IOException(
- "Buffer too small to contain the message: $dataLength > ${buffer.capacity() - buffer.position()}")
- buffer.mark()
- buffer.position(buffer.limit()) // restore old position
- buffer.limit(end)
- readBytes(buffer.limit())
- buffer.reset()
- }
-
- private fun ByteBuffer.tryPosition(newPosition: Int) {
- if (limit() < newPosition) throw EOFException("${limit()} < $newPosition")
- position(newPosition)
- }
-
- fun udpWrap(packet: ByteBuffer) = ByteBuffer.allocateDirect(3 + dest.size + packet.remaining()).apply {
- // header
- putShort(0) // reserved
- put(0) // fragment number
- put(dest)
- // data
- put(packet)
- flip()
- }
- fun udpReceiveBuffer(size: Int) = ByteBuffer.allocateDirect(headerReserved + size)
- fun udpUnwrap(packet: ByteBuffer) {
- packet.tryPosition(3)
- packet.tryPosition(6 + when (val type = packet.get()) {
- Socks5Message.SOCKS_ATYP_IPV4.toByte() -> 4
- Socks5Message.SOCKS_ATYP_DOMAINNAME.toByte() -> 1 + packet.get()
- Socks5Message.SOCKS_ATYP_IPV6.toByte() -> 16
- else -> throw IOException("Unsupported address type $type")
- })
- packet.mark()
- }
-}
diff --git a/mobile/src/main/res/raw/about.html b/mobile/src/main/res/raw/about.html
index e62e352a..6d5d988f 100644
--- a/mobile/src/main/res/raw/about.html
+++ b/mobile/src/main/res/raw/about.html
@@ -31,8 +31,6 @@ Open Source Licenses
kotlin: APL 2.0
material: APL 2.0
gson: APL 2.0
- dnsjava: BSD
- jsocks: LGPL 2.1
preferencex-simplemenu: APL 2.0
android-plugin-api-for-locale: APL 2.0
qrgen: APL 2.0