diff --git a/app/build.gradle b/app/build.gradle index 57228a164..4a3a3764b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,6 +96,7 @@ dependencies { kapt 'androidx.room:room-compiler:2.4.2' implementation 'com.squareup.okhttp3:okhttp:4.9.3' + implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' implementation 'com.squareup.okio:okio:3.1.0' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt new file mode 100644 index 000000000..7c3c2db6e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/network/DoHManager.kt @@ -0,0 +1,84 @@ +package org.koitharu.kotatsu.core.network + +import okhttp3.Cache +import okhttp3.Dns +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.dnsoverhttps.DnsOverHttps +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.utils.ext.printStackTraceDebug +import java.net.InetAddress +import java.net.UnknownHostException + +class DoHManager( + cache: Cache, + private val settings: AppSettings, +) : Dns { + + private val bootstrapClient = OkHttpClient.Builder().cache(cache).build() + + private var cachedDelegate: Dns? = null + private var cachedProvider: DoHProvider? = null + + override fun lookup(hostname: String): List { + return getDelegate().lookup(hostname) + } + + @Synchronized + private fun getDelegate(): Dns { + var delegate = cachedDelegate + val provider = settings.dnsOverHttps + if (delegate == null || provider != cachedProvider) { + delegate = createDelegate(provider) + cachedDelegate = delegate + cachedProvider = provider + } + return delegate + } + + private fun createDelegate(provider: DoHProvider): Dns = when (provider) { + DoHProvider.NONE -> Dns.SYSTEM + DoHProvider.GOOGLE -> DnsOverHttps.Builder().client(bootstrapClient) + .url("https://dns.google/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + listOfNotNull( + tryGetByIp("8.8.4.4"), + tryGetByIp("8.8.8.8"), + tryGetByIp("2001:4860:4860::8888"), + tryGetByIp("2001:4860:4860::8844"), + ) + ).build() + DoHProvider.CLOUDFLARE -> DnsOverHttps.Builder().client(bootstrapClient) + .url("https://cloudflare-dns.com/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + listOfNotNull( + tryGetByIp("162.159.36.1"), + tryGetByIp("162.159.46.1"), + tryGetByIp("1.1.1.1"), + tryGetByIp("1.0.0.1"), + tryGetByIp("162.159.132.53"), + tryGetByIp("2606:4700:4700::1111"), + tryGetByIp("2606:4700:4700::1001"), + tryGetByIp("2606:4700:4700::0064"), + tryGetByIp("2606:4700:4700::6400"), + ) + ).build() + DoHProvider.ADGUARD -> DnsOverHttps.Builder().client(bootstrapClient) + .url("https://dns-unfiltered.adguard.com/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + listOfNotNull( + tryGetByIp("94.140.14.140"), + tryGetByIp("94.140.14.141"), + tryGetByIp("2a10:50c0::1:ff"), + tryGetByIp("2a10:50c0::2:ff"), + ) + ).build() + } + + private fun tryGetByIp(ip: String): InetAddress? = try { + InetAddress.getByName(ip) + } catch (e: UnknownHostException) { + e.printStackTraceDebug() + null + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/DoHProvider.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/DoHProvider.kt new file mode 100644 index 000000000..e17db70a7 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/network/DoHProvider.kt @@ -0,0 +1,6 @@ +package org.koitharu.kotatsu.core.network + +enum class DoHProvider { + + NONE, GOOGLE, CLOUDFLARE, ADGUARD +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt index 48b009a33..2af4c215e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.core.network -import java.util.concurrent.TimeUnit import okhttp3.CookieJar import okhttp3.OkHttpClient import org.koin.dsl.bind @@ -8,17 +7,20 @@ import org.koin.dsl.module import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.parsers.MangaLoaderContext +import java.util.concurrent.TimeUnit val networkModule get() = module { single { AndroidCookieJar() } bind CookieJar::class single { + val cache = get().createHttpCache() OkHttpClient.Builder().apply { connectTimeout(20, TimeUnit.SECONDS) readTimeout(60, TimeUnit.SECONDS) writeTimeout(20, TimeUnit.SECONDS) cookieJar(get()) - cache(get().createHttpCache()) + dns(DoHManager(cache, get())) + cache(cache) addInterceptor(UserAgentInterceptor()) addInterceptor(CloudFlareInterceptor()) }.build() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index dd5f57cd5..62e47f141 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.callbackFlow import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.model.ZoomMode +import org.koitharu.kotatsu.core.network.DoHProvider import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.utils.ext.getEnumValue import org.koitharu.kotatsu.utils.ext.putEnumValue @@ -188,6 +189,9 @@ class AppSettings(context: Context) { get() = prefs.getBoolean(KEY_SEARCH_SINGLE_SOURCE, false) set(value) = prefs.edit { putBoolean(KEY_SEARCH_SINGLE_SOURCE, value) } + val dnsOverHttps: DoHProvider + get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE) + fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean { return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) { NETWORK_ALWAYS -> true @@ -303,6 +307,7 @@ class AppSettings(context: Context) { const val KEY_DOWNLOADS_PARALLELISM = "downloads_parallelism" const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown" const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible" + const val KEY_DOH = "doh" // About const val KEY_APP_UPDATE = "app_update" diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt index 8bfd5e39a..dab7b972e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt @@ -3,18 +3,22 @@ package org.koitharu.kotatsu.settings import android.content.SharedPreferences import android.os.Bundle import android.view.View +import androidx.preference.ListPreference import androidx.preference.Preference -import java.io.File import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog +import org.koitharu.kotatsu.core.network.DoHProvider import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.settings.utils.SliderPreference import org.koitharu.kotatsu.utils.ext.getStorageName +import org.koitharu.kotatsu.utils.ext.names +import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat import org.koitharu.kotatsu.utils.ext.viewLifecycleScope +import java.io.File class ContentSettingsFragment : BasePreferenceFragment(R.string.content), @@ -36,6 +40,10 @@ class ContentSettingsFragment : true } } + findPreference(AppSettings.KEY_DOH)?.run { + entryValues = enumValues().names() + setDefaultValueCompat(DoHProvider.NONE.name) + } bindRemoteSourcesSummary() } diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 03bed46af..4f56771f0 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -34,4 +34,10 @@ @string/only_using_wifi @string/never + + @string/disabled + Google + CloudFlare + AdGuard + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb74b8b83..d4f35e720 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -295,4 +295,5 @@ Bookmark added Undo Removed from history + DNS over HTTPS \ No newline at end of file diff --git a/app/src/main/res/xml/pref_content.xml b/app/src/main/res/xml/pref_content.xml index 53fee4124..7b48494ea 100644 --- a/app/src/main/res/xml/pref_content.xml +++ b/app/src/main/res/xml/pref_content.xml @@ -14,6 +14,12 @@ android:persistent="false" android:title="@string/suggestions" /> + +