Implemented DNS over HTTPS (Google JSON)

This commit is contained in:
PeratX 2018-06-21 23:57:05 +08:00
parent 1a8145971d
commit fdd28b6b6d
4 changed files with 145 additions and 6 deletions

View File

@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
* (at your option) any later version.
*/
public class HttpsIetfProvider extends HttpsProvider {
private static final String HTTPS_SUFFIX = "https://";
// implemented https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-11
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)

View File

@ -0,0 +1,139 @@
package org.itxtech.daedalus.provider;
import android.os.ParcelFileDescriptor;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import okhttp3.*;
import org.itxtech.daedalus.service.DaedalusVpnService;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsname.DnsName;
import org.minidns.record.*;
import org.pcap4j.packet.IpPacket;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* Daedalus Project
*
* @author iTX Technologies
* @link https://itxtech.org
* <p>
* This program 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.
*/
public class HttpsJsonProvider extends HttpsProvider {
/*
* Implemented https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format/
* https://developers.google.com/speed/public-dns/docs/dns-over-https
*/
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor((chain) -> {
Request original = chain.request();
Request request = original.newBuilder()
.header("Accept", "application/dns-json")
.build();
return chain.proceed(request);
})
.build();
public HttpsJsonProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) {
super(descriptor, service);
}
@Override
protected void sendRequestToServer(IpPacket parsedPacket, DnsMessage message, String uri) {
whqList.add(new WaitingHttpsRequest(parsedPacket) {
@Override
public void doRequest() {
final byte[] rawRequest = message.toArray();
Request request = new Request.Builder()
.url(HttpUrl.parse(HTTPS_SUFFIX + uri).newBuilder()
.addQueryParameter("name", message.getQuestion().name.toString())
.addQueryParameter("type", message.getQuestion().type.name())
.build())
.get()
.build();
HTTP_CLIENT.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
result = rawRequest;
completed = true;
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
JsonObject jsonObject = new JsonParser().parse(response.body().string()).getAsJsonObject();
DnsMessage.Builder msg = message.asBuilder()
.setRecursionDesired(jsonObject.get("RD").getAsBoolean())
.setRecursionAvailable(jsonObject.get("RA").getAsBoolean())
.setAuthenticData(jsonObject.get("AD").getAsBoolean())
.setCheckingDisabled(jsonObject.get("CD").getAsBoolean());
if (jsonObject.has("Answer")) {
JsonArray answers = jsonObject.get("Answer").getAsJsonArray();
for (JsonElement answer : answers) {
JsonObject ans = answer.getAsJsonObject();
Record.TYPE type = Record.TYPE.getType(ans.get("type").getAsInt());
String data = ans.get("data").getAsString();
Data recordData = null;
switch (type) {
case A:
recordData = new A(data);
break;
case AAAA:
recordData = new AAAA(data);
break;
case CNAME:
recordData = new CNAME(data);
break;
case MX:
recordData = new MX(5, data);
break;
case SOA:
String[] sections = data.split(" ");
if (sections.length == 7) {
recordData = new SOA(sections[0], sections[1],
Long.valueOf(sections[2]), Integer.valueOf(sections[3]),
Integer.valueOf(sections[4]), Integer.valueOf(sections[5]),
Long.valueOf(sections[6]));
}
break;
case DNAME:
recordData = new DNAME(data);
break;
case NS:
recordData = new NS(DnsName.from(data));
break;
case TXT:
recordData = new TXT(data.getBytes());
break;
}
if (recordData != null) {
msg.addAnswer(new Record<>(ans.get("name").getAsString(),
type, 1,
ans.get("TTL").getAsLong(),
recordData));
}
}
}
result = msg.build().toArray();
completed = true;
}
}
});
}
});
}
}

View File

@ -36,6 +36,8 @@ import java.util.LinkedList;
* (at your option) any later version.
*/
abstract public class HttpsProvider extends Provider {
protected static final String HTTPS_SUFFIX = "https://";
private static final String TAG = "HttpsProvider";
protected final WhqList whqList = new WhqList();
@ -147,7 +149,8 @@ abstract public class HttpsProvider extends Provider {
}
if (!resolve(parsedPacket, dnsMsg) && uri != null) {
sendRequestToServer(parsedPacket, dnsMsg, uri);
sendRequestToServer(parsedPacket, dnsMsg.asBuilder().setId(0).build(), uri);
//SHOULD use a DNS ID of 0 in every DNS request (according to draft-ietf-doh-dns-over-https-11)
}
}
@ -166,9 +169,6 @@ abstract public class HttpsProvider extends Provider {
public abstract void doRequest();
}
/**
* Queue of WaitingOnSocketPacket, bound on time and space.
*/
public static class WhqList implements Iterable<WaitingHttpsRequest> {
private final LinkedList<WaitingHttpsRequest> list = new LinkedList<>();

View File

@ -31,7 +31,7 @@ public class ProviderPicker {
case DNS_QUERY_METHOD_HTTPS_IETF:
return new HttpsIetfProvider(descriptor, service);
case DNS_QUERY_METHOD_HTTPS_JSON:
break;
return new HttpsJsonProvider(descriptor, service);
case DNS_QUERY_METHOD_TLS:
break;
}