diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4855054f..5f2d43fe 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -90,7 +90,7 @@ android { } } } - namespace = "org.lsposed.manager" + namespace = defaultManagerPackageName } autoResConfig { diff --git a/app/src/main/java/org/lsposed/manager/App.java b/app/src/main/java/org/lsposed/manager/App.java index 3e5ce8b0..251a4064 100644 --- a/app/src/main/java/org/lsposed/manager/App.java +++ b/app/src/main/java/org/lsposed/manager/App.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Process; +import android.provider.Settings; import android.system.Os; import android.text.TextUtils; import android.util.Log; @@ -43,7 +44,7 @@ import org.lsposed.hiddenapibypass.HiddenApiBypass; import org.lsposed.manager.adapters.AppHelper; import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.ui.activity.CrashReportActivity; -import org.lsposed.manager.util.DoHDNS; +import org.lsposed.manager.util.CloudflareDNS; import org.lsposed.manager.util.ModuleUtil; import org.lsposed.manager.util.Telemetry; import org.lsposed.manager.util.ThemeUtil; @@ -86,7 +87,6 @@ public class App extends Application { static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // TODO: set specific class name HiddenApiBypass.addHiddenApiExemptions(""); } Looper.myQueue().addIdleHandler(() -> { @@ -101,19 +101,17 @@ public class App extends Application { Looper.myQueue().addIdleHandler(() -> { if (App.getInstance() == null || App.getExecutorService() == null) return true; - App.getExecutorService().submit(() -> { - AppHelper.getDenyList(false); - }); + App.getExecutorService().submit(() -> AppHelper.getDenyList(false)); return false; }); Looper.myQueue().addIdleHandler(() -> { if (App.getInstance() == null || App.getExecutorService() == null) return true; - App.getExecutorService().submit((Runnable) ModuleUtil::getInstance); + App.getExecutorService().submit(ModuleUtil::getInstance); return false; }); Looper.myQueue().addIdleHandler(() -> { if (App.getInstance() == null || App.getExecutorService() == null) return true; - App.getExecutorService().submit((Runnable) RepoLoader::getInstance); + App.getExecutorService().submit(RepoLoader::getInstance); return false; }); } @@ -139,7 +137,7 @@ public class App extends Application { } public static ExecutorService getExecutorService() { - return instance.executorService; + return executorService; } public static boolean isParasitic() { @@ -208,8 +206,11 @@ public class App extends Application { instance = this; pref = PreferenceManager.getDefaultSharedPreferences(this); - if ("CN".equals(Locale.getDefault().getCountry())) { - if (!pref.contains("doh")) { + if (!pref.contains("doh")) { + var name = "private_dns_mode"; + if ("hostname".equals(Settings.Global.getString(getContentResolver(), name))) { + pref.edit().putBoolean("doh", false).apply(); + } else { pref.edit().putBoolean("doh", true).apply(); } } @@ -264,26 +265,24 @@ public class App extends Application { @NonNull public static OkHttpClient getOkHttpClient() { - if (okHttpClient == null) { - OkHttpClient.Builder builder = new OkHttpClient.Builder().cache(getOkHttpCache()); - builder.addInterceptor(chain -> { - var request = chain.request().newBuilder(); - request.header("User-Agent", TAG); - return chain.proceed(request.build()); - }); - HttpLoggingInterceptor log = new HttpLoggingInterceptor(); + if (okHttpClient != null) return okHttpClient; + var builder = new OkHttpClient.Builder() + .cache(getOkHttpCache()) + .dns(new CloudflareDNS()); + if (BuildConfig.DEBUG) { + var log = new HttpLoggingInterceptor(); log.setLevel(HttpLoggingInterceptor.Level.HEADERS); - if (BuildConfig.DEBUG) builder.addInterceptor(log); - okHttpClient = builder.dns(new DoHDNS(builder.build())).build(); + builder.addInterceptor(log); } + okHttpClient = builder.build(); return okHttpClient; } @NonNull - private static Cache getOkHttpCache() { - if (okHttpCache == null) { - okHttpCache = new Cache(new File(App.getInstance().getCacheDir(), "http_cache"), 50L * 1024L * 1024L); - } + public static Cache getOkHttpCache() { + if (okHttpCache != null) return okHttpCache; + long size50MiB = 50 * 1024 * 1024; + okHttpCache = new Cache(new File(instance.getCacheDir(), "http_cache"), size50MiB); return okHttpCache; } diff --git a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java index b60a757c..42d81d9d 100644 --- a/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/org/lsposed/manager/ui/fragment/SettingsFragment.java @@ -48,6 +48,7 @@ import org.lsposed.manager.databinding.FragmentSettingsBinding; import org.lsposed.manager.repo.RepoLoader; import org.lsposed.manager.ui.activity.MainActivity; import org.lsposed.manager.util.BackupUtils; +import org.lsposed.manager.util.CloudflareDNS; import org.lsposed.manager.util.LangList; import org.lsposed.manager.util.NavUtil; import org.lsposed.manager.util.ShortcutUtil; @@ -285,6 +286,22 @@ public class SettingsFragment extends BaseFragment { }); } + MaterialSwitchPreference prefDoH = findPreference("doh"); + if (prefDoH != null) { + var dns = (CloudflareDNS) App.getOkHttpClient().dns(); + if (!dns.noProxy) { + prefDoH.setEnabled(false); + prefDoH.setVisible(false); + var group = prefDoH.getParent(); + assert group != null; + group.setVisible(false); + } + prefDoH.setOnPreferenceChangeListener((p, v) -> { + dns.DoH = (boolean) v; + return true; + }); + } + SimpleMenuPreference language = findPreference("language"); if (language != null) { var tag = language.getValue(); diff --git a/app/src/main/java/org/lsposed/manager/util/CloudflareDNS.java b/app/src/main/java/org/lsposed/manager/util/CloudflareDNS.java new file mode 100644 index 00000000..e0bf6214 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/util/CloudflareDNS.java @@ -0,0 +1,66 @@ +package org.lsposed.manager.util; + +import android.os.Build; + +import androidx.annotation.NonNull; + +import org.lsposed.manager.App; + +import java.net.InetAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.UnknownHostException; +import java.util.List; + +import okhttp3.ConnectionSpec; +import okhttp3.Dns; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.dnsoverhttps.DnsOverHttps; +import okhttp3.internal.platform.Platform; + +public final class CloudflareDNS implements Dns { + + private static final HttpUrl url = HttpUrl.get("https://cloudflare-dns.com/dns-query"); + public boolean DoH = App.getPreferences().getBoolean("doh", false); + public boolean noProxy = ProxySelector.getDefault().select(url.uri()).get(0) == Proxy.NO_PROXY; + private final Dns cloudflare; + + public CloudflareDNS() { + var trustManager = Platform.get().platformTrustManager(); + var tls = ConnectionSpec.RESTRICTED_TLS; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + //noinspection deprecation + tls = new ConnectionSpec.Builder(tls) + .supportsTlsExtensions(false) + .build(); + } + var builder = new DnsOverHttps.Builder() + .resolvePrivateAddresses(true) + .url(HttpUrl.get("https://cloudflare-dns.com/dns-query")) + .client(new OkHttpClient.Builder() + .cache(App.getOkHttpCache()) + .sslSocketFactory(new NoSniFactory(), trustManager) + .connectionSpecs(List.of(tls)) + .build()); + try { + builder.bootstrapDnsHosts(List.of( + InetAddress.getByName("1.1.1.1"), + InetAddress.getByName("1.0.0.1"), + InetAddress.getByName("2606:4700:4700::1111"), + InetAddress.getByName("2606:4700:4700::1001"))); + } catch (UnknownHostException ignored) { + } + cloudflare = builder.build(); + } + + @NonNull + @Override + public List lookup(@NonNull String hostname) throws UnknownHostException { + if (DoH && noProxy) { + return cloudflare.lookup(hostname); + } else { + return SYSTEM.lookup(hostname); + } + } +} diff --git a/app/src/main/java/org/lsposed/manager/util/DoHDNS.java b/app/src/main/java/org/lsposed/manager/util/DoHDNS.java deleted file mode 100644 index 81e76934..00000000 --- a/app/src/main/java/org/lsposed/manager/util/DoHDNS.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of LSPosed. - * - * LSPosed 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. - * - * LSPosed 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 LSPosed. If not, see . - * - * Copyright (C) 2020 EdXposed Contributors - * Copyright (C) 2021 LSPosed Contributors - */ - -package org.lsposed.manager.util; - -import androidx.annotation.NonNull; - -import org.lsposed.manager.App; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.List; -import java.util.Locale; - -import okhttp3.Dns; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.dnsoverhttps.DnsOverHttps; - -public class DoHDNS implements Dns { - - private static DnsOverHttps cloudflare; - private static DnsOverHttps tuna; - private static DnsOverHttps dnspod; - - public DoHDNS(OkHttpClient client) { - var builder = new DnsOverHttps.Builder().resolvePrivateAddresses(true).client(client); - cloudflare = builder.url(HttpUrl.get("https://cloudflare-dns.com/dns-query")).build(); - tuna = builder.url(HttpUrl.get("https://101.6.6.6:8443/dns-query")).build(); - dnspod = builder.url(HttpUrl.get("https://doh.pub/dns-query")).build(); - } - - @NonNull - @Override - public List lookup(@NonNull String hostname) throws UnknownHostException { - if (App.getPreferences().getBoolean("doh", false)) { - try { - return cloudflare.lookup(hostname); - } catch (UnknownHostException e) { - try { - if ("CN".equals(Locale.getDefault().getCountry())) - return tuna.lookup(hostname); - } catch (UnknownHostException exception) { - try { - return dnspod.lookup(hostname); - } catch (UnknownHostException ignored) { - } - } - } - } - return SYSTEM.lookup(hostname); - } -} diff --git a/app/src/main/java/org/lsposed/manager/util/NoSniFactory.java b/app/src/main/java/org/lsposed/manager/util/NoSniFactory.java new file mode 100644 index 00000000..034c6096 --- /dev/null +++ b/app/src/main/java/org/lsposed/manager/util/NoSniFactory.java @@ -0,0 +1,59 @@ +package org.lsposed.manager.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import javax.net.ssl.SSLSocketFactory; + +public final class NoSniFactory extends SSLSocketFactory { + private static final SSLSocketFactory defaultFactory = (SSLSocketFactory) getDefault(); + @SuppressWarnings("deprecation") + private static final android.net.SSLCertificateSocketFactory openSSLSocket = + (android.net.SSLCertificateSocketFactory) android.net.SSLCertificateSocketFactory + .getDefault(1000); + + @Override + public String[] getDefaultCipherSuites() { + return defaultFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return defaultFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return config(defaultFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return config(defaultFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return config(defaultFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return config(defaultFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return config(defaultFactory.createSocket(address, port, localAddress, localPort)); + } + + private Socket config(Socket socket) { + try { + openSSLSocket.setHostname(socket, null); + openSSLSocket.setUseSessionTickets(socket, true); + } catch (IllegalArgumentException ignored) { + } + return socket; + } +}