Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ buildscript {
sdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
preferencexVersion = '1.0.0'
junitVersion = '4.12'
androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1'
Expand Down
3 changes: 1 addition & 2 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,12 @@ dependencies {
api project(':plugin')
api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api 'androidx.preference:preference:1.0.0'
api 'androidx.preference:preference:1.1.0-alpha05'
api "androidx.room:room-runtime:$roomVersion"
api 'androidx.work:work-runtime-ktx:2.0.1'
api 'com.crashlytics.sdk.android:crashlytics:2.10.0'
api 'com.google.firebase:firebase-config:17.0.0'
api 'com.google.firebase:firebase-core:16.0.9'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'dnsjava:dnsjava:2.1.8'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
api 'org.connectbot.jsocks:jsocks:1.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class VpnRequestActivity : AppCompatActivity() {
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != REQUEST_CONNECT) return super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) Core.startService() else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ package com.github.shadowsocks.bg
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.net.LocalDnsServer
import com.github.shadowsocks.net.Socks5Endpoint
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import kotlinx.coroutines.CoroutineScope
import java.net.InetSocketAddress
import java.net.URI
Expand All @@ -49,7 +51,8 @@ object LocalDnsService {
val dns = URI("dns://${profile.remoteDns}")
LocalDnsServer(this::resolver,
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress).apply {
DataStore.proxyAddress,
HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "")).apply {
tcp = !profile.udpdns
when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> {
Expand Down
39 changes: 39 additions & 0 deletions core/src/main/java/com/github/shadowsocks/net/HostsFile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <[email protected]> *
* Copyright (C) 2019 by Mygod Studio <[email protected]> *
* *
* 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 com.github.shadowsocks.utils.computeIfAbsentCompat
import com.github.shadowsocks.utils.parseNumericAddress
import java.net.InetAddress

class HostsFile(input: String = "") {
private val map = mutableMapOf<String, MutableSet<InetAddress>>()
init {
for (line in input.lineSequence()) {
val entries = line.substringBefore('#').splitToSequence(' ', '\t').filter { it.isNotEmpty() }
val address = entries.firstOrNull()?.parseNumericAddress() ?: continue
for (hostname in entries.drop(1)) map.computeIfAbsentCompat(hostname) { LinkedHashSet(1) }.add(address)
}
}

val configuredHostnames get() = map.size
fun resolve(hostname: String) = map[hostname]?.shuffled() ?: emptyList()
}
32 changes: 22 additions & 10 deletions core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ 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) : CoroutineScope {
private val remoteDns: Socks5Endpoint,
private val proxy: SocketAddress,
private val hosts: HostsFile) : CoroutineScope {
/**
* Forward all requests to remote and ignore localResolver.
*/
Expand All @@ -70,7 +72,18 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
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<InetAddress>) =
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 -> throw IllegalStateException("Unsupported address $address")
}, Section.ANSWER)
}.toWire())
}

private val monitor = ChannelMonitor()

override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t -> printLog(t) }
Expand Down Expand Up @@ -101,10 +114,16 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
return supervisorScope {
val remote = async { withTimeout(TIMEOUT) { forward(packet) } }
try {
if (forwardOnly || request.header.opcode != Opcode.QUERY) return@supervisorScope remote.await()
if (request.header.opcode != Opcode.QUERY) return@supervisorScope remote.await()
val question = request.question
if (question?.type != Type.A) return@supervisorScope remote.await()
val host = question.name.toString(true)
val hostsResults = hosts.resolve(host)
if (hostsResults.isNotEmpty()) {
remote.cancel()
return@supervisorScope cookDnsResponse(request, hostsResults)
}
if (forwardOnly) return@supervisorScope remote.await()
if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await()
val localResults = try {
withTimeout(TIMEOUT) { GlobalScope.async(Dispatchers.IO) { localResolver(host) }.await() }
Expand All @@ -117,14 +136,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
if (localResults.isEmpty()) return@supervisorScope remote.await()
if (localIpMatcher.isEmpty() || localIpMatcher.any { subnet -> localResults.any(subnet::matches) }) {
remote.cancel()
ByteBuffer.wrap(prepareDnsResponse(request).apply {
header.setFlag(Flags.RA.toInt()) // recursion available
for (address in localResults) addRecord(when (address) {
is Inet4Address -> ARecord(question.name, DClass.IN, TTL, address)
is Inet6Address -> AAAARecord(question.name, DClass.IN, TTL, address)
else -> throw IllegalStateException("Unsupported address $address")
}, Section.ANSWER)
}.toWire())
cookDnsResponse(request, localResults.asIterable())
} else remote.await()
} catch (e: Exception) {
remote.cancel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
publicStore.registerChangeListener(this)
}

override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) {
Key.id -> if (directBootAware) DirectBoot.update()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <[email protected]> *
* Copyright (C) 2019 by Mygod Studio <[email protected]> *
* *
* 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.preference

import android.graphics.Typeface
import android.text.InputFilter
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.preference.EditTextPreference

object EditTextPreferenceModifiers {
object Monospace : EditTextPreference.OnBindEditTextListener {
override fun onBindEditText(editText: EditText) {
editText.typeface = Typeface.MONOSPACE
}
}

object Port : EditTextPreference.OnBindEditTextListener {
private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5))

override fun onBindEditText(editText: EditText) {
editText.inputType = EditorInfo.TYPE_CLASS_NUMBER
editText.filters = portLengthFilter
editText.setSingleLine()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <[email protected]> *
* Copyright (C) 2019 by Mygod Studio <[email protected]> *
* *
* 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.preference

import androidx.preference.EditTextPreference
import androidx.preference.Preference
import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.HostsFile

object HostsSummaryProvider : Preference.SummaryProvider<EditTextPreference> {
override fun provideSummary(preference: EditTextPreference?): CharSequence {
val count = HostsFile(preference!!.text ?: "").configuredHostnames
return preference.context.resources.getQuantityString(R.plurals.hosts_summary, count, count)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ package com.github.shadowsocks.preference
import androidx.preference.PreferenceDataStore

interface OnPreferenceDataStoreChangeListener {
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?)
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ object Key {
const val dirty = "profileDirty"

const val tfo = "tcp_fastopen"
const val hosts = "hosts"
const val assetUpdateTime = "assetUpdateTime"

// TV specific values
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/com/github/shadowsocks/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ private val parseNumericAddress by lazy {
*
* Bug: https://issuetracker.google.com/issues/123456213
*/
fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
fun String.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress }

fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT >= 24)
computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) }

fun HttpURLConnection.disconnectFromMain() {
if (Build.VERSION.SDK_INT >= 26) disconnect() else GlobalScope.launch(Dispatchers.IO) { disconnect() }
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
<string name="tcp_fastopen_summary">Toggling might require ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="tcp_fastopen_failure">Toggle failed</string>
<plurals name="hosts_summary">
<item quantity="one">1 hostname configured</item>
<item quantity="other">%d hostnames configured</item>
</plurals>
<string name="udp_dns">Send DNS over UDP</string>
<string name="udp_dns_summary">Requires UDP forwarding on server side</string>
<string name="udp_fallback">UDP Fallback</string>
Expand All @@ -82,7 +86,6 @@
<!-- alert category -->
<string name="profile_empty">Please select a profile</string>
<string name="proxy_empty">Proxy/Password should not be empty</string>
<string name="file_manager_missing">Please install a file manager like MiXplorer</string>
<string name="connect">Connect</string>

<!-- menu category -->
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.enableR8=true
# android.enableR8.fullMode=true
android.enableR8.fullMode=true
android.useAndroidX=true

# When configured, Gradle will run in incubating parallel mode.
Expand Down
4 changes: 2 additions & 2 deletions mobile/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ androidExtensions {

dependencies {
implementation project(':core')
implementation "androidx.browser:browser:1.0.0"
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-vision:17.0.2'
implementation 'com.google.firebase:firebase-ads:17.2.0'
implementation "com.takisoft.preferencex:preferencex-simplemenu:$preferencexVersion"
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation 'net.glxn.qrgen:android:2.0'
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
Expand Down
5 changes: 0 additions & 5 deletions mobile/src/main/java/com/github/shadowsocks/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,12 @@ package com.github.shadowsocks
import android.app.Application
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.IconListPreference
import com.takisoft.preferencex.PreferenceFragmentCompat

class App : Application() {
override fun onCreate() {
super.onCreate()
Core.init(this, MainActivity::class)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
}

override fun onConfigurationChanged(newConfig: Configuration) {
Expand Down
Loading