Drop domain fronting and other network hacks since #1147

This commit is contained in:
tarsin committed 2023-06-17 16:37:51 +08:00
1 parent cf95e7b034
commit 2d20da70b8
8 files changed
+6 -248

No files matched your search

-1
View File
@@ -226,7 +226,6 @@ dependencies {
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11")) implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11"))
implementation("com.squareup.okhttp3:okhttp-android") implementation("com.squareup.okhttp3:okhttp-android")
implementation("com.squareup.okhttp3:okhttp-coroutines") implementation("com.squareup.okhttp3:okhttp-coroutines")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps")
implementation("com.squareup.okio:okio-jvm:3.3.0") implementation("com.squareup.okio:okio-jvm:3.3.0")
@@ -18,6 +18,7 @@ package com.hippo.ehviewer
import android.app.Application import android.app.Application
import android.content.ComponentCallbacks2 import android.content.ComponentCallbacks2
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.LruCache import androidx.collection.LruCache
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
@@ -26,11 +27,8 @@ import coil.decode.ImageDecoderDecoder
import coil.util.DebugLogger import coil.util.DebugLogger
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.hippo.ehviewer.client.EhCookieStore import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhDns
import com.hippo.ehviewer.client.EhSSLSocketFactory
import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.data.GalleryDetail import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.install
import com.hippo.ehviewer.coil.MergeInterceptor import com.hippo.ehviewer.coil.MergeInterceptor
import com.hippo.ehviewer.coil.diskCache import com.hippo.ehviewer.coil.diskCache
import com.hippo.ehviewer.coil.imageLoader import com.hippo.ehviewer.coil.imageLoader
@@ -48,6 +46,9 @@ import com.hippo.ehviewer.yorozuya.FileUtils
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import okhttp3.AsyncDns
import okhttp3.Dns
import okhttp3.android.AndroidAsyncDns
import okio.Path.Companion.toOkioPath import okio.Path.Companion.toOkioPath
import splitties.arch.room.roomDb import splitties.arch.room.roomDb
import splitties.init.appCtx import splitties.init.appCtx
@@ -146,15 +147,15 @@ class EhApplication : Application(), ImageLoaderFactory {
companion object { companion object {
val nonCacheOkHttpClient by lazy { val nonCacheOkHttpClient by lazy {
val systemDns = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6) else Dns.SYSTEM
httpClient { httpClient {
cookieJar(EhCookieStore) cookieJar(EhCookieStore)
dns(EhDns) dns(systemDns)
addInterceptor( addInterceptor(
ChuckerInterceptor.Builder(appCtx).apply { ChuckerInterceptor.Builder(appCtx).apply {
alwaysReadResponseBody(false) alwaysReadResponseBody(false)
}.build(), }.build(),
) )
if (Settings.dF) install(EhSSLSocketFactory)
} }
} }
@@ -1,52 +0,0 @@
package com.hippo.ehviewer
import com.hippo.ehviewer.client.hosts
import com.hippo.ehviewer.client.hostsDsl
val builtInHosts = hostsDsl {
hosts("ehgt.org", "gt0.ehgt.org", "gt1.ehgt.org", "gt2.ehgt.org", "gt3.ehgt.org") {
"37.48.89.44" blockedInCN false
"81.171.10.48" blockedInCN false
"178.162.139.24" blockedInCN false
"178.162.140.212" blockedInCN false
"2001:1af8:4700:a062:8::47de" blockedInCN false
"2001:1af8:4700:a062:9::47de" blockedInCN true
"2001:1af8:4700:a0c9:4::47de" blockedInCN false
"2001:1af8:4700:a0c9:3::47de" blockedInCN true
}
hosts("e-hentai.org") {
"104.20.134.21" blockedInCN false
"104.20.135.21" blockedInCN false
"172.67.0.127" blockedInCN false
}
hosts("exhentai.org") {
"178.175.128.252" blockedInCN false
"178.175.129.252" blockedInCN false
"178.175.129.254" blockedInCN false
"178.175.128.254" blockedInCN false
"178.175.132.20" blockedInCN false
"178.175.132.22" blockedInCN false
}
hosts("s.exhentai.org") {
"178.175.129.254" blockedInCN false
"178.175.128.254" blockedInCN false
"178.175.132.22" blockedInCN false
}
hosts("repo.e-hentai.org") {
"94.100.28.57" blockedInCN true
"94.100.29.73" blockedInCN true
}
hosts("forums.e-hentai.org") {
"94.100.18.243" blockedInCN false
}
hosts("ul.ehgt.org") {
"94.100.24.82" blockedInCN true
"94.100.24.72" blockedInCN true
}
hosts("raw.githubusercontent.com") {
"151.101.0.133" blockedInCN true
"151.101.64.133" blockedInCN false
"151.101.128.133" blockedInCN false
"151.101.192.133" blockedInCN false
}
}
@@ -12,8 +12,6 @@ import kotlin.reflect.KProperty
object Settings : DataStorePreferences(null) { object Settings : DataStorePreferences(null) {
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations" private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
private const val KEY_BUILT_IN_HOSTS = "built_in_hosts_2"
private const val KEY_DOMAIN_FRONTING = "domain_fronting"
var downloadScheme by stringOrNullPref("image_scheme", null) var downloadScheme by stringOrNullPref("image_scheme", null)
var downloadAuthority by stringOrNullPref("image_authority", null) var downloadAuthority by stringOrNullPref("image_authority", null)
@@ -48,14 +46,11 @@ object Settings : DataStorePreferences(null) {
var saveParseErrorBody by boolPref("save_parse_error_body", true) var saveParseErrorBody by boolPref("save_parse_error_body", true)
var saveCrashLog by boolPref("save_crash_log", true) var saveCrashLog by boolPref("save_crash_log", true)
var security by boolPref("require_unlock", false) var security by boolPref("require_unlock", false)
var builtInHosts by boolPref(KEY_BUILT_IN_HOSTS, false)
var removeImageFiles by boolPref("include_pic", true) var removeImageFiles by boolPref("include_pic", true)
var needSignIn by boolPref("need_sign_in", true) var needSignIn by boolPref("need_sign_in", true)
var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() } var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() }
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false) var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
var dF by boolPref(KEY_DOMAIN_FRONTING, false)
var downloadOriginImage by boolPref("download_origin_image", false) var downloadOriginImage by boolPref("download_origin_image", false)
var bypassVpn by boolPref("bypass_vpn", true)
var thumbSizeDp by intPref("thumb_size_", 120) var thumbSizeDp by intPref("thumb_size_", 120)
var favLocalCount by intPref("fav_local", 0) var favLocalCount by intPref("fav_local", 0)
var favCloudCount by intPref("fav_cloud", 0) var favCloudCount by intPref("fav_cloud", 0)
@@ -78,8 +73,6 @@ object Settings : DataStorePreferences(null) {
init { init {
if ("CN" == Locale.getDefault().country) { if ("CN" == Locale.getDefault().country) {
edit { edit {
if (KEY_BUILT_IN_HOSTS !in prefs) builtInHosts = true
if (KEY_DOMAIN_FRONTING !in prefs) dF = true
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations = true if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations = true
} }
} }
@@ -1,34 +0,0 @@
package com.hippo.ehviewer.client
import android.os.Build
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.builtInHosts
import com.hippo.ehviewer.ui.settings.EhDoH
import okhttp3.AsyncDns
import okhttp3.Dns
import okhttp3.android.AndroidAsyncDns
import java.net.InetAddress
private typealias HostsMap = MutableMap<String, List<InetAddress>>
fun hostsDsl(builder: HostsMap.() -> Unit): HostsMap = mutableMapOf<String, List<InetAddress>>().apply(builder)
fun interface HostMapBuilder {
infix fun String.blockedInCN(boolean: Boolean)
}
fun HostsMap.hosts(vararg hosts: String, builder: HostMapBuilder.() -> Unit) = apply {
hosts.forEach { host ->
fun String.toInetAddress() = InetAddress.getByName(this).let { InetAddress.getByAddress(host, it.address) }
mutableListOf<InetAddress>().apply {
HostMapBuilder { if (!(Settings.dF && it)) add(toInetAddress()) }.apply(builder)
put(host, this)
}
}
}
val systemDns = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6) else Dns.SYSTEM
object EhDns : Dns {
override fun lookup(hostname: String): List<InetAddress> = builtInHosts[hostname].takeIf { Settings.builtInHosts } ?: EhDoH.lookup(hostname) ?: systemDns.lookup(hostname)
}
@@ -1,55 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer 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.
*
* EhViewer 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 EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.client
import android.util.Log
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.builtInHosts
import okhttp3.OkHttpClient
import java.net.InetAddress
import java.net.Socket
import java.security.KeyStore
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
private const val EXCEPTIONAL_DOMAIN = "hath.network"
private val sslSocketFactory = SSLSocketFactory.getDefault() as SSLSocketFactory
object EhSSLSocketFactory : SSLSocketFactory() {
override fun getDefaultCipherSuites(): Array<String> = sslSocketFactory.defaultCipherSuites
override fun getSupportedCipherSuites(): Array<String> = sslSocketFactory.supportedCipherSuites
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
val address = s.inetAddress.hostAddress.takeIf { host in builtInHosts || EXCEPTIONAL_DOMAIN in host || host in Settings.dohUrl }
Log.d("EhSSLSocketFactory", "Host: $host Address: $address")
return sslSocketFactory.createSocket(s, address ?: host, port, autoClose) as SSLSocket
}
override fun createSocket(host: String, port: Int): Socket = sslSocketFactory.createSocket(host, port)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket = sslSocketFactory.createSocket(host, port, localHost, localPort)
override fun createSocket(host: InetAddress, port: Int): Socket = sslSocketFactory.createSocket(host, port)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket = sslSocketFactory.createSocket(address, port, localAddress, localPort)
}
fun OkHttpClient.Builder.install(sslSocketFactory: SSLSocketFactory) = apply {
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())!!
factory.init(null as KeyStore?)
val manager = factory.trustManagers!!
val trustManager = manager.filterIsInstance<X509TrustManager>().first()
sslSocketFactory(sslSocketFactory, trustManager)
}
@@ -24,14 +24,10 @@ import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState import android.content.pm.verify.domain.DomainVerificationUserState
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.ImageDecoder import android.graphics.ImageDecoder
import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -201,9 +197,6 @@ class MainActivity : EhActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
if (Settings.dF && Settings.bypassVpn) {
bypassVpn()
}
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@@ -251,19 +244,6 @@ class MainActivity : EhActivity() {
} }
} }
private fun bypassVpn() {
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
capabilities?.let {
if (it.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
val builder = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
connectivityManager.registerNetworkCallback(builder.build(), mNetworkCallback)
}
}
}
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@Throws(PackageManager.NameNotFoundException::class) @Throws(PackageManager.NameNotFoundException::class)
private fun checkAppLinkVerify() { private fun checkAppLinkVerify() {
@@ -484,25 +464,4 @@ class MainActivity : EhActivity() {
super.onProvideAssistContent(outContent) super.onProvideAssistContent(outContent)
mShareUrl?.let { outContent?.webUri = Uri.parse(mShareUrl) } mShareUrl?.let { outContent?.webUri = Uri.parse(mShareUrl) }
} }
private val mNetworkCallback = object : ConnectivityManager.NetworkCallback() {
private val TAG = "mNetworkCallback"
override fun onAvailable(network: Network) {
Log.d(TAG, "onAvailable: $network")
connectivityManager.bindProcessToNetwork(network)
availableNetworks.add(network)
}
override fun onLost(network: Network) {
Log.d(TAG, "onLost: $network")
val activeNetwork = availableNetworks.last()
availableNetworks.remove(network)
if (network == activeNetwork) {
connectivityManager.bindProcessToNetwork(
availableNetworks.takeIf { it.isNotEmpty() }?.last(),
)
}
}
}
} }
@@ -1,7 +1,6 @@
package com.hippo.ehviewer.ui.settings package com.hippo.ehviewer.ui.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
@@ -32,15 +31,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.hippo.ehviewer.AppConfig import com.hippo.ehviewer.AppConfig
import com.hippo.ehviewer.BuildConfig import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.data.FavListUrlBuilder import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.client.systemDns
import com.hippo.ehviewer.ui.LocalNavController import com.hippo.ehviewer.ui.LocalNavController
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
import com.hippo.ehviewer.ui.tools.observed import com.hippo.ehviewer.ui.tools.observed
import com.hippo.ehviewer.util.LogCat import com.hippo.ehviewer.util.LogCat
import com.hippo.ehviewer.util.ReadableTime import com.hippo.ehviewer.util.ReadableTime
@@ -48,10 +44,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moe.tarsin.coroutines.runSuspendCatching import moe.tarsin.coroutines.runSuspendCatching
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.dnsoverhttps.DnsOverHttps
import java.io.File import java.io.File
import java.net.InetAddress
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
@@ -133,37 +126,6 @@ fun AdvancedScreen() {
entryValueRes = R.array.app_language_entry_values, entryValueRes = R.array.app_language_entry_values,
value = Settings::language, value = Settings::language,
) )
SwitchPreference(
title = stringResource(id = R.string.settings_advanced_built_in_hosts_title),
value = Settings::builtInHosts,
)
Preference(title = stringResource(id = R.string.settings_advanced_dns_over_http_title)) {
val builder = EditTextDialogBuilder(context, Settings.dohUrl, context.getString(R.string.settings_advanced_dns_over_http_hint))
builder.setTitle(R.string.settings_advanced_dns_over_http_title)
builder.setPositiveButton(android.R.string.ok, null)
val dialog = builder.create().apply { show() }
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val text = builder.text.trim()
runCatching {
doh = if (text.isNotBlank()) buildDoHDNS(text) else null
}.onFailure {
builder.setError("Invalid URL!")
}.onSuccess {
Settings.dohUrl = text
dialog.dismiss()
}
}
}
SwitchPreference(
title = stringResource(id = R.string.settings_advanced_domain_fronting_title),
summary = stringResource(id = R.string.settings_advanced_domain_fronting_summary),
value = Settings::dF,
)
SwitchPreference(
title = stringResource(id = R.string.settings_advanced_bypass_vpn_title),
summary = stringResource(id = R.string.settings_advanced_bypass_vpn_summary),
value = Settings::bypassVpn,
)
SwitchPreference( SwitchPreference(
title = stringResource(id = R.string.preload_thumb_aggressively), title = stringResource(id = R.string.preload_thumb_aggressively),
value = Settings::preloadThumbAggressively, value = Settings::preloadThumbAggressively,
@@ -265,18 +227,3 @@ fun AdvancedScreen() {
} }
} }
} }
private fun buildDoHDNS(url: String): DnsOverHttps {
return DnsOverHttps.Builder().apply {
client(EhApplication.okHttpClient)
url(url.toHttpUrl())
post(true)
systemDns(systemDns)
}.build()
}
private var doh: DnsOverHttps? = Settings.dohUrl.runCatching { buildDoHDNS(this) }.getOrNull()
object EhDoH {
fun lookup(hostname: String): List<InetAddress>? = doh?.runCatching { lookup(hostname).takeIf { it.isNotEmpty() } }?.onFailure { it.printStackTrace() }?.getOrNull()
}