Update DoH (#2293)
This commit is contained in:
parent
583a5a541d
commit
673d97573c
|
|
@ -90,7 +90,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace = "org.lsposed.manager"
|
namespace = defaultManagerPackageName
|
||||||
}
|
}
|
||||||
|
|
||||||
autoResConfig {
|
autoResConfig {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
@ -43,7 +44,7 @@ import org.lsposed.hiddenapibypass.HiddenApiBypass;
|
||||||
import org.lsposed.manager.adapters.AppHelper;
|
import org.lsposed.manager.adapters.AppHelper;
|
||||||
import org.lsposed.manager.repo.RepoLoader;
|
import org.lsposed.manager.repo.RepoLoader;
|
||||||
import org.lsposed.manager.ui.activity.CrashReportActivity;
|
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.ModuleUtil;
|
||||||
import org.lsposed.manager.util.Telemetry;
|
import org.lsposed.manager.util.Telemetry;
|
||||||
import org.lsposed.manager.util.ThemeUtil;
|
import org.lsposed.manager.util.ThemeUtil;
|
||||||
|
|
@ -86,7 +87,6 @@ public class App extends Application {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
// TODO: set specific class name
|
|
||||||
HiddenApiBypass.addHiddenApiExemptions("");
|
HiddenApiBypass.addHiddenApiExemptions("");
|
||||||
}
|
}
|
||||||
Looper.myQueue().addIdleHandler(() -> {
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
|
|
@ -101,19 +101,17 @@ public class App extends Application {
|
||||||
|
|
||||||
Looper.myQueue().addIdleHandler(() -> {
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
App.getExecutorService().submit(() -> {
|
App.getExecutorService().submit(() -> AppHelper.getDenyList(false));
|
||||||
AppHelper.getDenyList(false);
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
Looper.myQueue().addIdleHandler(() -> {
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
App.getExecutorService().submit((Runnable) ModuleUtil::getInstance);
|
App.getExecutorService().submit(ModuleUtil::getInstance);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
Looper.myQueue().addIdleHandler(() -> {
|
Looper.myQueue().addIdleHandler(() -> {
|
||||||
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
if (App.getInstance() == null || App.getExecutorService() == null) return true;
|
||||||
App.getExecutorService().submit((Runnable) RepoLoader::getInstance);
|
App.getExecutorService().submit(RepoLoader::getInstance);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +137,7 @@ public class App extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExecutorService getExecutorService() {
|
public static ExecutorService getExecutorService() {
|
||||||
return instance.executorService;
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isParasitic() {
|
public static boolean isParasitic() {
|
||||||
|
|
@ -208,8 +206,11 @@ public class App extends Application {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
pref = PreferenceManager.getDefaultSharedPreferences(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();
|
pref.edit().putBoolean("doh", true).apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,26 +265,24 @@ public class App extends Application {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static OkHttpClient getOkHttpClient() {
|
public static OkHttpClient getOkHttpClient() {
|
||||||
if (okHttpClient == null) {
|
if (okHttpClient != null) return okHttpClient;
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder().cache(getOkHttpCache());
|
var builder = new OkHttpClient.Builder()
|
||||||
builder.addInterceptor(chain -> {
|
.cache(getOkHttpCache())
|
||||||
var request = chain.request().newBuilder();
|
.dns(new CloudflareDNS());
|
||||||
request.header("User-Agent", TAG);
|
if (BuildConfig.DEBUG) {
|
||||||
return chain.proceed(request.build());
|
var log = new HttpLoggingInterceptor();
|
||||||
});
|
|
||||||
HttpLoggingInterceptor log = new HttpLoggingInterceptor();
|
|
||||||
log.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
log.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||||
if (BuildConfig.DEBUG) builder.addInterceptor(log);
|
builder.addInterceptor(log);
|
||||||
okHttpClient = builder.dns(new DoHDNS(builder.build())).build();
|
|
||||||
}
|
}
|
||||||
|
okHttpClient = builder.build();
|
||||||
return okHttpClient;
|
return okHttpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static Cache getOkHttpCache() {
|
public static Cache getOkHttpCache() {
|
||||||
if (okHttpCache == null) {
|
if (okHttpCache != null) return okHttpCache;
|
||||||
okHttpCache = new Cache(new File(App.getInstance().getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
|
long size50MiB = 50 * 1024 * 1024;
|
||||||
}
|
okHttpCache = new Cache(new File(instance.getCacheDir(), "http_cache"), size50MiB);
|
||||||
return okHttpCache;
|
return okHttpCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import org.lsposed.manager.databinding.FragmentSettingsBinding;
|
||||||
import org.lsposed.manager.repo.RepoLoader;
|
import org.lsposed.manager.repo.RepoLoader;
|
||||||
import org.lsposed.manager.ui.activity.MainActivity;
|
import org.lsposed.manager.ui.activity.MainActivity;
|
||||||
import org.lsposed.manager.util.BackupUtils;
|
import org.lsposed.manager.util.BackupUtils;
|
||||||
|
import org.lsposed.manager.util.CloudflareDNS;
|
||||||
import org.lsposed.manager.util.LangList;
|
import org.lsposed.manager.util.LangList;
|
||||||
import org.lsposed.manager.util.NavUtil;
|
import org.lsposed.manager.util.NavUtil;
|
||||||
import org.lsposed.manager.util.ShortcutUtil;
|
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");
|
SimpleMenuPreference language = findPreference("language");
|
||||||
if (language != null) {
|
if (language != null) {
|
||||||
var tag = language.getValue();
|
var tag = language.getValue();
|
||||||
|
|
|
||||||
|
|
@ -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<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
|
||||||
|
if (DoH && noProxy) {
|
||||||
|
return cloudflare.lookup(hostname);
|
||||||
|
} else {
|
||||||
|
return SYSTEM.lookup(hostname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* 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<InetAddress> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue