Unverified Commit 141a3076 authored by Mygod's avatar Mygod Committed by GitHub

Deprecate hosts (#2502)

parent 198434ea
......@@ -35,10 +35,8 @@ import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.readableMessage
import com.google.firebase.analytics.FirebaseAnalytics
......@@ -239,7 +237,7 @@ object BaseService {
val isVpnService get() = false
suspend fun startProcesses(hosts: HostsFile) {
suspend fun startProcesses() {
val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()
?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir
val udpFallback = data.udpFallback
......@@ -315,7 +313,6 @@ object BaseService {
listOfNotNull(data.proxy, data.udpFallback).forEach { it.trafficMonitor?.persistStats(it.profile.id) }
suspend fun preInit() { }
suspend fun getActiveNetwork() = if (Build.VERSION.SDK_INT >= 23) Core.connectivity.activeNetwork else null
suspend fun resolver(host: String) = DnsResolverCompat.resolveOnActiveNetwork(host)
suspend fun rawResolver(query: ByteArray) = DnsResolverCompat.resolveRawOnActiveNetwork(query)
suspend fun openConnection(url: URL) = url.openConnection()
......@@ -355,9 +352,8 @@ object BaseService {
try {
Executable.killAll() // clean up old processes
preInit()
val hosts = HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "")
proxy.init(this@Interface, hosts)
data.udpFallback?.init(this@Interface, hosts)
proxy.init(this@Interface)
data.udpFallback?.init(this@Interface)
if (profile.route == Acl.CUSTOM_RULES) try {
withContext(Dispatchers.IO) {
Acl.customRules.flatten(10, this@Interface::openConnection).also {
......@@ -372,7 +368,7 @@ object BaseService {
Timber.w(it)
stopRunner(false, it.readableMessage)
}
startProcesses(hosts)
startProcesses()
proxy.scheduleUpdate()
data.udpFallback?.scheduleUpdate()
......
......@@ -26,8 +26,6 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.AclSyncer
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.preference.DataStore
......@@ -52,7 +50,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) }
private var scheduleConfigUpdate = false
suspend fun init(service: BaseService.Interface, hosts: HostsFile) {
suspend fun init(service: BaseService.Interface) {
if (profile.isSponsored) {
scheduleConfigUpdate = true
val mdg = MessageDigest.getInstance("SHA-1")
......@@ -86,23 +84,10 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
// it's hard to resolve DNS on a specific interface so we'll do it here
if (profile.host.parseNumericAddress() == null) {
profile.host = hosts.resolve(profile.host).run {
if (isEmpty()) try {
profile.host = try {
service.resolver(profile.host).firstOrNull()
} catch (_: IOException) {
null
} else {
val network = service.getActiveNetwork() ?: throw UnknownHostException()
val hasIpv4 = DnsResolverCompat.haveIpv4(network)
val hasIpv6 = DnsResolverCompat.haveIpv6(network)
firstOrNull {
when (it) {
is Inet4Address -> hasIpv4
is Inet6Address -> hasIpv6
else -> error(it)
}
}
}
}?.hostAddress ?: throw UnknownHostException()
}
}
......
......@@ -23,7 +23,6 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.HostsFile
import com.github.shadowsocks.preference.DataStore
import java.io.File
......@@ -57,9 +56,9 @@ redsocks {
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf"))
}
override suspend fun startProcesses(hosts: HostsFile) {
override suspend fun startProcesses() {
startRedsocksDaemon()
super.startProcesses(hosts)
super.startProcesses()
}
override fun onDestroy() {
......
......@@ -34,7 +34,10 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.*
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.DefaultNetworkListener
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.closeQuietly
......@@ -137,7 +140,6 @@ class VpnService : BaseVpnService(), BaseService.Interface {
}
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }
override suspend fun getActiveNetwork() = DefaultNetworkListener.get()
override suspend fun resolver(host: String) = DnsResolverCompat.resolve(DefaultNetworkListener.get(), host)
override suspend fun rawResolver(query: ByteArray) =
// no need to listen for network here as this is only used for forwarding local DNS queries.
......@@ -145,9 +147,9 @@ class VpnService : BaseVpnService(), BaseService.Interface {
DnsResolverCompat.resolveRaw(underlyingNetwork ?: throw IOException("no network"), query)
override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url)
override suspend fun startProcesses(hosts: HostsFile) {
override suspend fun startProcesses() {
worker = ProtectWorker().apply { start() }
super.startProcesses(hosts)
super.startProcesses()
sendFd(startVpn())
}
......
......@@ -29,14 +29,10 @@ import android.os.CancellationSignal
import android.os.Looper
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.closeQuietly
import com.github.shadowsocks.utils.int
import com.github.shadowsocks.utils.parseNumericAddress
import kotlinx.coroutines.*
import org.xbill.DNS.*
import timber.log.Timber
import java.io.FileDescriptor
import java.io.IOException
import java.net.Inet4Address
......@@ -58,42 +54,6 @@ sealed class DnsResolverCompat {
}
}
/**
* Based on: https://android.googlesource.com/platform/frameworks/base/+/9f97f97/core/java/android/net/util/DnsUtils.java#341
*/
private val address4 = "8.8.8.8".parseNumericAddress()!!
private val address6 = "2000::".parseNumericAddress()!!
suspend fun haveIpv4(network: Network) = checkConnectivity(network, OsConstants.AF_INET, address4)
suspend fun haveIpv6(network: Network) = checkConnectivity(network, OsConstants.AF_INET6, address6)
private suspend fun checkConnectivity(network: Network, domain: Int, addr: InetAddress) = try {
val socket = Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP)
try {
instance.bindSocket(network, socket)
instance.connectUdp(socket, addr)
} finally {
socket.closeQuietly()
}
true
} catch (e: IOException) {
if ((e.cause as? ErrnoException)?.errno == OsConstants.EPERM) checkConnectivity(network, addr) else false
} catch (_: ErrnoException) {
false
} catch (e: ReflectiveOperationException) {
check(Build.VERSION.SDK_INT < 23)
Timber.w(e)
checkConnectivity(network, addr)
}
private fun checkConnectivity(network: Network, addr: InetAddress): Boolean {
return Core.connectivity.getLinkProperties(network)?.routes?.any {
try {
it.matches(addr)
} catch (e: RuntimeException) {
Timber.w(e)
false
}
} == true
}
override fun bindSocket(network: Network, socket: FileDescriptor) = instance.bindSocket(network, socket)
override suspend fun resolve(network: Network, host: String) = instance.resolve(network, host)
override suspend fun resolveOnActiveNetwork(host: String) = instance.resolveOnActiveNetwork(host)
......
/*******************************************************************************
* *
* 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 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.computeIfAbsent(hostname) { LinkedHashSet(1) }.add(address)
}
}
val configuredHostnames get() = map.size
fun resolve(hostname: String) = map[hostname]?.shuffled() ?: emptyList()
}
/*******************************************************************************
* *
* 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.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)
}
}
......@@ -64,7 +64,6 @@ object Key {
const val dirty = "profileDirty"
const val hosts = "hosts"
const val assetUpdateTime = "assetUpdateTime"
// TV specific values
......
......@@ -20,8 +20,6 @@
package com.github.shadowsocks
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.View
......@@ -30,24 +28,14 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.plugin.showAllowingStateLoss
import com.github.shadowsocks.preference.BrowsableEditTextPreferenceDialogFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.EditTextPreferenceModifiers
import com.github.shadowsocks.preference.HostsSummaryProvider
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.utils.remove
import com.github.shadowsocks.widget.MainListListener
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
companion object {
private const val REQUEST_BROWSE = 1
}
private val hosts by lazy { findPreference<EditTextPreference>(Key.hosts)!! }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal()
......@@ -63,8 +51,6 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
true
} else canToggleLocked.remove()
hosts.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
hosts.summaryProvider = HostsSummaryProvider
val serviceMode = findPreference<Preference>(Key.serviceMode)!!
val portProxy = findPreference<EditTextPreference>(Key.portProxy)!!
portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
......@@ -78,7 +64,6 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
}
val listener: (BaseService.State) -> Unit = {
val stopped = it == BaseService.State.Stopped
hosts.isEnabled = stopped
serviceMode.isEnabled = stopped
portProxy.isEnabled = stopped
portLocalDns.isEnabled = stopped
......@@ -96,29 +81,6 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
listView.setOnApplyWindowInsetsListener(MainListListener)
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
if (preference == hosts) BrowsableEditTextPreferenceDialogFragment().apply {
setKey(hosts.key)
setTargetFragment(this@GlobalSettingsPreferenceFragment, REQUEST_BROWSE)
}.showAllowingStateLoss(parentFragmentManager, hosts.key) else super.onDisplayPreferenceDialog(preference)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_BROWSE -> {
if (resultCode != Activity.RESULT_OK) return
val activity = activity as MainActivity
try {
// we read and persist all its content here to avoid content URL permission issues
hosts.text = activity.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText()
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onDestroy() {
MainActivity.stateListener = null
super.onDestroy()
......
/*******************************************************************************
* *
* 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.preference
import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.preference.EditTextPreferenceDialogFragmentCompat
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
class BrowsableEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
fun setKey(key: String) {
arguments = bundleOf(Pair(ARG_KEY, key))
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setNeutralButton(R.string.browse) { _, _ ->
val activity = activity as MainActivity
try {
targetFragment!!.startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}, targetRequestCode)
return@setNeutralButton
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
activity.snackbar(activity.getString(R.string.file_manager_missing)).show()
}
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M21,5c-1.11,-0.35 -2.33,-0.5 -3.5,-0.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.25 0.25,0.5 0.5,0.5c0.1,0 0.15,-0.05 0.25,-0.05C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05c0.1,0.05 0.15,0.05 0.25,0.05c0.25,0 0.5,-0.25 0.5,-0.5V6C22.4,5.55 21.75,5.25 21,5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,10.5c0.88,0 1.73,0.09 2.5,0.26V9.24C19.21,9.09 18.36,9 17.5,9c-1.7,0 -3.24,0.29 -4.5,0.83v1.66C14.13,10.85 15.7,10.5 17.5,10.5z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M13,12.49v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26V11.9c-0.79,-0.15 -1.64,-0.24 -2.5,-0.24C15.8,11.66 14.26,11.96 13,12.49z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,14.33c-1.7,0 -3.24,0.29 -4.5,0.83v1.66c1.13,-0.64 2.7,-0.99 4.5,-0.99c0.88,0 1.73,0.09 2.5,0.26v-1.52C19.21,14.41 18.36,14.33 17.5,14.33z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:initialExpandedChildrenCount="3">
app:initialExpandedChildrenCount="2">
<SwitchPreference
app:key="isAutoConnect"
app:icon="@drawable/ic_communication_phonelink_ring"
......@@ -11,10 +11,6 @@
app:icon="@drawable/ic_action_lock"
app:summary="@string/direct_boot_aware_summary"
app:title="@string/direct_boot_aware"/>
<EditTextPreference
app:key="hosts"
app:icon="@drawable/ic_maps_menu_book"
app:title="hosts"/>
<com.takisoft.preferencex.SimpleMenuPreference
app:key="serviceMode"
......
......@@ -44,7 +44,6 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.EditTextPreferenceModifiers
import com.github.shadowsocks.preference.HostsSummaryProvider
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas
......@@ -58,13 +57,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private const val REQUEST_CONNECT = 1
private const val REQUEST_REPLACE_PROFILES = 2
private const val REQUEST_EXPORT_PROFILES = 3
private const val REQUEST_HOSTS = 4
}
private lateinit var fab: ListPreference
private lateinit var stats: Preference
private lateinit var controlImport: Preference
private lateinit var hosts: EditTextPreference
private lateinit var serviceMode: Preference
private lateinit var shareOverLan: Preference
private lateinit var portProxy: EditTextPreference
......@@ -111,7 +108,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
this.state = state
val stopped = state == BaseService.State.Stopped
controlImport.isEnabled = stopped
hosts.isEnabled = stopped
serviceMode.isEnabled = stopped
shareOverLan.isEnabled = stopped
portProxy.isEnabled = stopped
......@@ -148,8 +144,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
true
}
hosts = findPreference(Key.hosts)!!
hosts.summaryProvider = HostsSummaryProvider
serviceMode = findPreference(Key.serviceMode)!!
shareOverLan = findPreference(Key.shareOverLan)!!
portProxy = findPreference(Key.portProxy)!!
......@@ -245,11 +239,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
else -> super.onPreferenceTreeClick(preference)
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
if (preference != hosts || startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).setType("*/*"), REQUEST_HOSTS))
super.onDisplayPreferenceDialog(preference)
}
private fun startFilesForResult(intent: Intent, requestCode: Int): Boolean {
try {
startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
......@@ -291,16 +280,6 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
}
REQUEST_HOSTS -> {
if (resultCode != Activity.RESULT_OK) return
val context = requireContext()
try {
// we read and persist all its content here to avoid content URL permission issues
hosts.text = context.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText()
} catch (e: Exception) {
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
......
......@@ -20,14 +20,11 @@
<PreferenceCategory
app:key="settings"
app:title="@string/settings"
app:initialExpandedChildrenCount="2">
app:initialExpandedChildrenCount="1">
<SwitchPreference
app:key="isAutoConnect"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect"/>
<EditTextPreference
app:key="hosts"
app:title="hosts"/>
<ListPreference
app:key="serviceMode"
......
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