Add dns over https.

pull/1/head
Cuke 4 years ago
parent 04408dab4b
commit bd4778063b
  1. 8
      app/src/main/java/com/github/catvod/crawler/Spider.java
  2. 3
      app/src/main/java/com/github/tvbox/osc/server/ControlManager.java
  3. 11
      app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java
  4. 43
      app/src/main/java/com/github/tvbox/osc/ui/fragment/ModelSettingFragment.java
  5. 190
      app/src/main/java/com/github/tvbox/osc/util/CrashHandler.java
  6. 1
      app/src/main/java/com/github/tvbox/osc/util/HawkConfig.java
  7. 55
      app/src/main/java/com/github/tvbox/osc/util/OkGoHelper.java
  8. 45
      app/src/main/java/okhttp3/dnsoverhttps/BootstrapDns.java
  9. 399
      app/src/main/java/okhttp3/dnsoverhttps/DnsOverHttps.java
  10. 139
      app/src/main/java/okhttp3/dnsoverhttps/DnsRecordCodec.java
  11. 2
      app/src/main/res/layout/fragment_model.xml

@ -2,11 +2,15 @@ package com.github.catvod.crawler;
import android.content.Context;
import com.github.tvbox.osc.util.OkGoHelper;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
import okhttp3.Dns;
public abstract class Spider {
public static JSONObject empty = new JSONObject();
@ -100,4 +104,8 @@ public abstract class Spider {
public boolean manualVideoCheck() {
return false;
}
public static Dns safeDns() {
return OkGoHelper.dnsOverHttps;
}
}

@ -15,6 +15,8 @@ import org.greenrobot.eventbus.EventBus;
import java.io.IOException;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
/**
* @author pj567
* @date :2021/1/4
@ -81,6 +83,7 @@ public class ControlManager {
});
try {
mServer.start();
IjkMediaPlayer.setDotPort(Hawk.get(HawkConfig.DOH_URL, 0) > 0, RemoteServer.serverPort);
break;
} catch (IOException ex) {
RemoteServer.serverPort++;

@ -8,12 +8,14 @@ import android.os.Environment;
import com.github.tvbox.osc.R;
import com.github.tvbox.osc.api.ApiConfig;
import com.github.tvbox.osc.event.ServerEvent;
import com.github.tvbox.osc.util.OkGoHelper;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.greenrobot.eventbus.EventBus;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -137,6 +139,15 @@ public class RemoteServer extends NanoHTTPD {
} catch (Throwable th) {
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, th.getMessage());
}
} else if (fileName.equals("/dns-query")) {
String name = session.getParms().get("name");
byte[] rs = null;
try {
rs = OkGoHelper.dnsOverHttps.lookupHttpsForwardSync(name);
} catch (Throwable th) {
rs = new byte[0];
}
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/dns-message", new ByteArrayInputStream(rs), rs.length);
}
} else if (session.getMethod() == Method.POST) {
Map<String, String> files = new HashMap<String, String>();

@ -22,6 +22,7 @@ import com.github.tvbox.osc.ui.dialog.SelectDialog;
import com.github.tvbox.osc.ui.dialog.XWalkInitDialog;
import com.github.tvbox.osc.util.FastClickCheckUtil;
import com.github.tvbox.osc.util.HawkConfig;
import com.github.tvbox.osc.util.OkGoHelper;
import com.github.tvbox.osc.util.PlayerHelper;
import com.github.tvbox.osc.util.XWalkUtils;
import com.orhanobut.hawk.Hawk;
@ -32,6 +33,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import okhttp3.HttpUrl;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
/**
* @author pj567
* @date :2020/12/23
@ -47,6 +51,7 @@ public class ModelSettingFragment extends BaseLazyFragment {
private TextView tvXWalkDown;
private TextView tvApi;
private TextView tvHomeApi;
private TextView tvDns;
public static ModelSettingFragment newInstance() {
return new ModelSettingFragment().setArguments();
@ -72,11 +77,13 @@ public class ModelSettingFragment extends BaseLazyFragment {
tvXWalkDown = findViewById(R.id.tvXWalkDown);
tvApi = findViewById(R.id.tvApi);
tvHomeApi = findViewById(R.id.tvHomeApi);
tvDns = findViewById(R.id.tvDns);
tvMediaCodec.setText(Hawk.get(HawkConfig.IJK_CODEC, ""));
tvDebugOpen.setText(Hawk.get(HawkConfig.DEBUG_OPEN, false) ? "已打开" : "已关闭");
tvParseWebView.setText(Hawk.get(HawkConfig.PARSE_WEBVIEW, true) ? "系统自带" : "XWalkView");
tvXWalkDown.setText(XWalkUtils.xWalkLibExist(mContext) ? "已下载" : "未下载");
tvApi.setText(Hawk.get(HawkConfig.API_URL, ""));
tvDns.setText(OkGoHelper.dnsHttpsList.get(Hawk.get(HawkConfig.DOH_URL, 0)));
tvHomeApi.setText(ApiConfig.get().getHomeSourceBean().getName());
tvScale.setText(PlayerHelper.getScaleName(Hawk.get(HawkConfig.PLAY_SCALE, 0)));
tvPlay.setText(PlayerHelper.getPlayerName(Hawk.get(HawkConfig.PLAY_TYPE, 0)));
@ -169,6 +176,42 @@ public class ModelSettingFragment extends BaseLazyFragment {
}
}
});
findViewById(R.id.llDns).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FastClickCheckUtil.check(v);
int dohUrl = Hawk.get(HawkConfig.DOH_URL, 0);
SelectDialog<String> dialog = new SelectDialog<>(mActivity);
dialog.setTip("请选择安全DNS");
dialog.setAdapter(new SelectDialogAdapter.SelectDialogInterface<String>() {
@Override
public void click(String value, int pos) {
tvDns.setText(OkGoHelper.dnsHttpsList.get(pos));
Hawk.put(HawkConfig.DOH_URL, pos);
String url = OkGoHelper.getDohUrl(pos);
OkGoHelper.dnsOverHttps.setUrl(url.isEmpty() ? null : HttpUrl.get(url));
IjkMediaPlayer.toggleDotPort(pos > 0);
}
@Override
public String getDisplay(String val) {
return val;
}
}, new DiffUtil.ItemCallback<String>() {
@Override
public boolean areItemsTheSame(@NonNull @NotNull String oldItem, @NonNull @NotNull String newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areContentsTheSame(@NonNull @NotNull String oldItem, @NonNull @NotNull String newItem) {
return oldItem.equals(newItem);
}
}, OkGoHelper.dnsHttpsList, dohUrl);
dialog.show();
}
});
findViewById(R.id.llApi).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

@ -1,190 +0,0 @@
package com.github.tvbox.osc.util;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author acer
* @date 2018/9/10
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static volatile CrashHandler instance;
private Context mContext;
private PendingIntent restartIntent;
private Thread.UncaughtExceptionHandler mDefaultHandler;
private String mExceptionInfo;
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
private DateFormat formatterFodder = new SimpleDateFormat("yyyyMMdd");
private ConcurrentHashMap<String, String> mCrashInfo = new ConcurrentHashMap<>();
private CrashHandler() {
}
public static CrashHandler getInstance() {
if (instance == null) {
synchronized (CrashHandler.class) {
if (instance == null) {
instance = new CrashHandler();
}
}
}
return instance;
}
public void init(Context context, PendingIntent pendingIntent) {
mContext = context;
restartIntent = pendingIntent;
//保存一份系统默认的CrashHandler
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
//使用我们自定义的异常处理器替换程序默认的
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
if (!catchCrashException(e) && mDefaultHandler != null) {
//没有自定义的CrashHandler的时候就调用系统默认的异常处理方式
mDefaultHandler.uncaughtException(t, e);
} else {
//退出应用
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// 退出程序并在2s后重启
AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 2000, restartIntent);
AppManager.getInstance().appExit(1);
}
}
private boolean catchCrashException(Throwable ex) {
if (ex == null) {
return false;
}
collectInfo(ex);
//上传崩溃信息
uploadCrashInfo();
return true;
}
/**
* 获取异常信息和设备参数信息
*/
private void collectInfo(Throwable ex) {
mExceptionInfo = collectExceptionInfo(ex);
try {
// 获得包管理器
PackageManager mPackageManager = mContext.getPackageManager();
// 得到该应用的信息,即主Activity
PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
this.mCrashInfo.put("PackageName", mContext.getPackageName());
if (mPackageInfo != null) {
String versionName = mPackageInfo.versionName == null ? "null" : mPackageInfo.versionName;
String versionCode = mPackageInfo.versionCode + "";
this.mCrashInfo.put("VersionName", versionName);
this.mCrashInfo.put("VersionCode", versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// 反射机制
Field[] mFields = Build.class.getDeclaredFields();
// 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
for (Field field : mFields) {
try {
field.setAccessible(true);
mCrashInfo.put(field.getName(), field.get("").toString());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private String saveCrashInfo2File(StringBuilder sb) {
try {
long timestamp = System.currentTimeMillis();
String time = formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String path = "/sdcard/movie/crash/" + formatterFodder.format(new Date()) + "/";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
fos.close();
}
return fileName;
} catch (Exception e) {
}
return null;
}
/**
* 获取捕获异常的信息
*/
private String collectExceptionInfo(Throwable ex) {
Writer mWriter = new StringWriter();
PrintWriter mPrintWriter = new PrintWriter(mWriter);
ex.printStackTrace(mPrintWriter);
ex.printStackTrace();
Throwable mThrowable = ex.getCause();
// 迭代栈队列把所有的异常信息写入writer中
while (mThrowable != null) {
mThrowable.printStackTrace(mPrintWriter);
// 换行 每个个异常栈之间换行
mPrintWriter.append("\r\n");
mThrowable = mThrowable.getCause();
}
// 记得关闭
mPrintWriter.close();
return mWriter.toString();
}
/**
* 将HashMap遍历转换成StringBuffer
*/
@NonNull
private static StringBuilder getInfoStr(ConcurrentHashMap<String, String> info) {
StringBuilder mStringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : info.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
mStringBuilder.append(key + "=" + value + "\r\n");
}
return mStringBuilder;
}
private void uploadCrashInfo() {
StringBuilder mStringBuilder = getInfoStr(mCrashInfo);
mStringBuilder.append(mExceptionInfo);
saveCrashInfo2File(mStringBuilder);
}
}

@ -18,4 +18,5 @@ public class HawkConfig {
public static final String PLAY_RENDER = "play_render"; //0 texture 2
public static final String PLAY_SCALE = "play_scale"; //0 texture 2
public static final String PLAY_TIME_STEP = "play_time_step"; //0 texture 2
public static final String DOH_URL = "doh_url";
}

@ -7,14 +7,19 @@ import com.lzy.okgo.https.HttpsUtils;
import com.lzy.okgo.interceptor.HttpLoggingInterceptor;
import com.orhanobut.hawk.Hawk;
import java.io.File;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.dnsoverhttps.DnsOverHttps;
import xyz.doikki.videoplayer.exo.ExoMediaSourceHelper;
public class OkGoHelper {
@ -43,11 +48,59 @@ public class OkGoHelper {
} catch (Throwable th) {
th.printStackTrace();
}
builder.dns(dnsOverHttps);
ExoMediaSourceHelper.getInstance(App.getInstance()).setOkClient(builder.build());
}
public static DnsOverHttps dnsOverHttps = null;
public static ArrayList<String> dnsHttpsList = new ArrayList<>();
public static String getDohUrl(int type) {
switch (type) {
case 1: {
return "https://doh.pub/dns-query";
}
case 2: {
return "https://dns.alidns.com/dns-query";
}
case 3: {
return "https://doh.360.cn/dns-query";
}
}
return "";
}
static void initDnsOverHttps() {
dnsHttpsList.add("关闭");
dnsHttpsList.add("腾讯");
dnsHttpsList.add("阿里");
dnsHttpsList.add("360");
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor("OkExoPlayer");
if (Hawk.get(HawkConfig.DEBUG_OPEN, false)) {
loggingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY);
loggingInterceptor.setColorLevel(Level.INFO);
} else {
loggingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.NONE);
loggingInterceptor.setColorLevel(Level.OFF);
}
builder.addInterceptor(loggingInterceptor);
try {
setOkHttpSsl(builder);
} catch (Throwable th) {
th.printStackTrace();
}
builder.cache(new Cache(new File(App.getInstance().getCacheDir().getAbsolutePath(), "dohcache"), 10 * 1024 * 1024));
OkHttpClient dohClient = builder.build();
String dohUrl = getDohUrl(Hawk.get(HawkConfig.DOH_URL, 0));
dnsOverHttps = new DnsOverHttps.Builder().client(dohClient).url(dohUrl.isEmpty() ? null : HttpUrl.get(dohUrl)).build();
}
public static void init() {
initDnsOverHttps();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor("OkGo");
@ -68,6 +121,7 @@ public class OkGoHelper {
builder.writeTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS);
builder.connectTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS);
builder.dns(dnsOverHttps);
try {
setOkHttpSsl(builder);
} catch (Throwable th) {
@ -77,6 +131,7 @@ public class OkGoHelper {
OkHttpClient okHttpClient = builder.build();
OkGo.getInstance().setOkHttpClient(okHttpClient);
initExoOkHttpClient();
}

@ -0,0 +1,45 @@
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.dnsoverhttps;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import okhttp3.Dns;
/**
* Internal Bootstrap DNS implementation for handling initial connection to DNS over HTTPS server.
*
* Returns hardcoded results for the known host.
*/
final class BootstrapDns implements Dns {
private final String dnsHostname;
private final List<InetAddress> dnsServers;
BootstrapDns(String dnsHostname, List<InetAddress> dnsServers) {
this.dnsHostname = dnsHostname;
this.dnsServers = dnsServers;
}
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (!this.dnsHostname.equals(hostname)) {
throw new UnknownHostException(
"BootstrapDns called for " + hostname + " instead of " + dnsHostname);
}
return dnsServers;
}
}

@ -0,0 +1,399 @@
/*
* Copyright (C) 2018 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.dnsoverhttps;
import androidx.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dns;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.Util;
import okhttp3.internal.platform.Platform;
import okhttp3.internal.publicsuffix.PublicSuffixDatabase;
import okio.ByteString;
/**
* DNS over HTTPS implementation.
* <p>
* Implementation of https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-13
*
* <blockquote>A DNS API client encodes a single DNS query into an HTTP request
* using either the HTTP GET or POST method and the other requirements
* of this section. The DNS API server defines the URI used by the
* request through the use of a URI Template.</blockquote>
*
* <h3>Warning: This is a non-final API.</h3>
*
* <p><strong>As of OkHttp 3.11, this feature is an unstable preview: the API is subject to change,
* and the implementation is incomplete. We expect that OkHttp 3.12 or 3.13 will finalize this API.
* Until then, expect API and behavior changes when you update your OkHttp dependency.</strong>
*/
public class DnsOverHttps implements Dns {
public static final MediaType DNS_MESSAGE = MediaType.get("application/dns-message");
public static final int MAX_RESPONSE_SIZE = 64 * 1024;
private final OkHttpClient client;
private HttpUrl url;
private final boolean includeIPv6;
private final boolean post;
private final boolean resolvePrivateAddresses;
private final boolean resolvePublicAddresses;
DnsOverHttps(Builder builder) {
if (builder.client == null) {
throw new NullPointerException("client not set");
}
if (builder.url == null) {
// throw new NullPointerException("url not set");
}
this.url = builder.url;
this.includeIPv6 = builder.includeIPv6;
this.post = builder.post;
this.resolvePrivateAddresses = builder.resolvePrivateAddresses;
this.resolvePublicAddresses = builder.resolvePublicAddresses;
this.client = builder.client.newBuilder().dns(buildBootstrapClient(builder)).build();
}
public void setUrl(HttpUrl newUrl) {
this.url = newUrl;
}
private static Dns buildBootstrapClient(Builder builder) {
List<InetAddress> hosts = builder.bootstrapDnsHosts;
if (hosts != null) {
return new BootstrapDns(builder.url.host(), hosts);
} else {
return builder.systemDns;
}
}
public HttpUrl url() {
return url;
}
public boolean post() {
return post;
}
public boolean includeIPv6() {
return includeIPv6;
}
public OkHttpClient client() {
return client;
}
public boolean resolvePrivateAddresses() {
return resolvePrivateAddresses;
}
public boolean resolvePublicAddresses() {
return resolvePublicAddresses;
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (this.url == null)
return Dns.SYSTEM.lookup(hostname);
if (!resolvePrivateAddresses || !resolvePublicAddresses) {
boolean privateHost = isPrivateHost(hostname);
if (privateHost && !resolvePrivateAddresses) {
throw new UnknownHostException("private hosts not resolved");
}
if (!privateHost && !resolvePublicAddresses) {
throw new UnknownHostException("public hosts not resolved");
}
}
return lookupHttps(hostname);
}
public byte[] lookupHttpsForwardSync(String hostname) throws Throwable {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
byteArrayOutputStream.write(executeRequestsSync(hostname, DnsRecordCodec.TYPE_A));
} finally {
}
try {
byteArrayOutputStream.write(executeRequestsSync(hostname, DnsRecordCodec.TYPE_AAAA));
} finally {
}
return byteArrayOutputStream.toByteArray();
}
private List<InetAddress> lookupHttps(String hostname) throws UnknownHostException {
List<Call> networkRequests = new ArrayList<>(2);
List<Exception> failures = new ArrayList<>(2);
List<InetAddress> results = new ArrayList<>(5);
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_A);
if (includeIPv6) {
buildRequest(hostname, networkRequests, results, failures, DnsRecordCodec.TYPE_AAAA);
}
executeRequests(hostname, networkRequests, results, failures);
if (!results.isEmpty()) {
return results;
}
return Dns.SYSTEM.lookup(hostname);
// return throwBestFailure(hostname, failures);
}
private void buildRequest(String hostname, List<Call> networkRequests, List<InetAddress> results,
List<Exception> failures, int type) {
Request request = buildRequest(hostname, type);
Response response = getCacheOnlyResponse(request);
if (response != null) {
processResponse(response, hostname, results, failures);
} else {
networkRequests.add(client.newCall(request));
}
}
private byte[] executeRequestsSync(String hostname, int type) throws IOException {
Request request = buildRequest(hostname, type);
Response response = getCacheOnlyResponse(request);
if (response == null) {
response = client.newCall(request).execute();
}
return response.body().bytes();
}
private void executeRequests(final String hostname, List<Call> networkRequests,
final List<InetAddress> responses, final List<Exception> failures) {
final CountDownLatch latch = new CountDownLatch(networkRequests.size());
for (Call call : networkRequests) {
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
synchronized (failures) {
failures.add(e);
}
latch.countDown();
}
@Override
public void onResponse(Call call, Response response) {
processResponse(response, hostname, responses, failures);
latch.countDown();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
failures.add(e);
}
}
private void processResponse(Response response, String hostname, List<InetAddress> results,
List<Exception> failures) {
try {
List<InetAddress> addresses = readResponse(hostname, response);
synchronized (results) {
results.addAll(addresses);
}
} catch (Exception e) {
synchronized (failures) {
failures.add(e);
}
}
}
private List<InetAddress> throwBestFailure(String hostname, List<Exception> failures)
throws UnknownHostException {
if (failures.size() == 0) {
throw new UnknownHostException(hostname);
}
Exception failure = failures.get(0);
if (failure instanceof UnknownHostException) {
throw (UnknownHostException) failure;
}
UnknownHostException unknownHostException = new UnknownHostException(hostname);
unknownHostException.initCause(failure);
for (int i = 1; i < failures.size(); i++) {
Util.addSuppressedIfPossible(unknownHostException, failures.get(i));
}
throw unknownHostException;
}
private @Nullable
Response getCacheOnlyResponse(Request request) {
if (!post && client.cache() != null) {
try {
Request cacheRequest = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
Response cacheResponse = client.newCall(cacheRequest).execute();
if (cacheResponse.code() != 504) {
return cacheResponse;
}
} catch (IOException ioe) {
// Failures are ignored as we can fallback to the network
// and hopefully repopulate the cache.
}
}
return null;
}
private List<InetAddress> readResponse(String hostname, Response response) throws Exception {
if (response.cacheResponse() == null && response.protocol() != Protocol.HTTP_2) {
Platform.get().log(Platform.WARN, "Incorrect protocol: " + response.protocol(), null);
}
try {
if (!response.isSuccessful()) {
throw new IOException("response: " + response.code() + " " + response.message());
}
ResponseBody body = response.body();
if (body.contentLength() > MAX_RESPONSE_SIZE) {
throw new IOException("response size exceeds limit ("
+ MAX_RESPONSE_SIZE
+ " bytes): "
+ body.contentLength()
+ " bytes");
}
ByteString responseBytes = body.source().readByteString();
return DnsRecordCodec.decodeAnswers(hostname, responseBytes);
} finally {
response.close();
}
}
private Request buildRequest(String hostname, int type) {
Request.Builder requestBuilder = new Request.Builder().header("Accept", DNS_MESSAGE.toString());
ByteString query = DnsRecordCodec.encodeQuery(hostname, type);
if (post) {
requestBuilder = requestBuilder.url(url).post(RequestBody.create(DNS_MESSAGE, query));
} else {
String encoded = query.base64Url().replace("=", "");
HttpUrl requestUrl = url.newBuilder().addQueryParameter("dns", encoded).build();
requestBuilder = requestBuilder.url(requestUrl);
}
return requestBuilder.build();
}
static boolean isPrivateHost(String host) {
return PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) == null;
}
public static final class Builder {
@Nullable
OkHttpClient client = null;
@Nullable
HttpUrl url = null;
boolean includeIPv6 = true;
boolean post = false;
Dns systemDns = Dns.SYSTEM;
@Nullable
List<InetAddress> bootstrapDnsHosts = null;
boolean resolvePrivateAddresses = false;
boolean resolvePublicAddresses = true;
public Builder() {
}
public DnsOverHttps build() {
return new DnsOverHttps(this);
}
public Builder client(OkHttpClient client) {
this.client = client;
return this;
}
public Builder url(HttpUrl url) {
this.url = url;
return this;
}
public Builder includeIPv6(boolean includeIPv6) {
this.includeIPv6 = includeIPv6;
return this;
}
public Builder post(boolean post) {
this.post = post;
return this;
}
public Builder resolvePrivateAddresses(boolean resolvePrivateAddresses) {
this.resolvePrivateAddresses = resolvePrivateAddresses;
return this;
}
public Builder resolvePublicAddresses(boolean resolvePublicAddresses) {
this.resolvePublicAddresses = resolvePublicAddresses;
return this;
}
public Builder bootstrapDnsHosts(@Nullable List<InetAddress> bootstrapDnsHosts) {
this.bootstrapDnsHosts = bootstrapDnsHosts;
return this;
}
public Builder bootstrapDnsHosts(InetAddress... bootstrapDnsHosts) {
return bootstrapDnsHosts(Arrays.asList(bootstrapDnsHosts));
}
public Builder systemDns(Dns systemDns) {
this.systemDns = systemDns;
return this;
}
}
}

@ -0,0 +1,139 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package okhttp3.dnsoverhttps;
import java.io.EOFException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import okio.Buffer;
import okio.ByteString;
import okio.Utf8;
/**
* Trivial Dns Encoder/Decoder, basically ripped from Netty full implementation.
*/
class DnsRecordCodec {
private static final byte SERVFAIL = 2;
private static final byte NXDOMAIN = 3;
public static final int TYPE_A = 0x0001;
public static final int TYPE_AAAA = 0x001c;
private static final int TYPE_PTR = 0x000c;
private static final Charset ASCII = Charset.forName("ASCII");
private DnsRecordCodec() {
}
public static ByteString encodeQuery(String host, int type) {
Buffer buf = new Buffer();
buf.writeShort(0); // query id
buf.writeShort(256); // flags with recursion
buf.writeShort(1); // question count
buf.writeShort(0); // answerCount
buf.writeShort(0); // authorityResourceCount
buf.writeShort(0); // additional
Buffer nameBuf = new Buffer();
final String[] labels = host.split("\\.");
for (String label : labels) {
long utf8ByteCount = Utf8.size(label);
if (utf8ByteCount != label.length()) {
throw new IllegalArgumentException("non-ascii hostname: " + host);
}
nameBuf.writeByte((byte) utf8ByteCount);
nameBuf.writeUtf8(label);
}
nameBuf.writeByte(0); // end
nameBuf.copyTo(buf, 0, nameBuf.size());
buf.writeShort(type);
buf.writeShort(1); // CLASS_IN
return buf.readByteString();
}
public static List<InetAddress> decodeAnswers(String hostname, ByteString byteString)
throws Exception {
List<InetAddress> result = new ArrayList<>();
Buffer buf = new Buffer();
buf.write(byteString);
buf.readShort(); // query id
final int flags = buf.readShort() & 0xffff;
if (flags >> 15 == 0) {
throw new IllegalArgumentException("not a response");
}
byte responseCode = (byte) (flags & 0xf);
if (responseCode == NXDOMAIN) {
throw new UnknownHostException(hostname + ": NXDOMAIN");
} else if (responseCode == SERVFAIL) {
throw new UnknownHostException(hostname + ": SERVFAIL");
}
final int questionCount = buf.readShort() & 0xffff;
final int answerCount = buf.readShort() & 0xffff;
buf.readShort(); // authority record count
buf.readShort(); // additional record count
for (int i = 0; i < questionCount; i++) {
skipName(buf); // name
buf.readShort(); // type
buf.readShort(); // class
}
for (int i = 0; i < answerCount; i++) {
skipName(buf); // name
int type = buf.readShort() & 0xffff;
buf.readShort(); // class
final long ttl = buf.readInt() & 0xffffffffL; // ttl
final int length = buf.readShort() & 0xffff;
if (type == TYPE_A || type == TYPE_AAAA) {
byte[] bytes = new byte[length];
buf.read(bytes);
result.add(InetAddress.getByAddress(bytes));
} else {
buf.skip(length);
}
}
return result;
}
private static void skipName(Buffer in) throws EOFException {
// 0 - 63 bytes
int length = in.readByte();
if (length < 0) {
// compressed name pointer, first two bits are 1
// drop second byte of compression offset
in.skip(1);
} else {
while (length > 0) {
// skip each part of the domain name
in.skip(length);
length = in.readByte();
}
}
}
}

@ -433,7 +433,7 @@
android:layout_weight="1" />
<TextView
android:id="@+id/tvDos"
android:id="@+id/tvDns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"

Loading…
Cancel
Save