Implemented DNS over HTTPS (IETF draft)
This commit is contained in:
parent
9932116dac
commit
1a8145971d
@ -0,0 +1,76 @@
|
||||
package org.itxtech.daedalus.provider;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Base64;
|
||||
import okhttp3.*;
|
||||
import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
import org.minidns.dnsmessage.DnsMessage;
|
||||
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 HttpsIetfProvider extends HttpsProvider {
|
||||
private static final String HTTPS_SUFFIX = "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-message")
|
||||
.build();
|
||||
|
||||
return chain.proceed(request);
|
||||
})
|
||||
.build();
|
||||
|
||||
public HttpsIetfProvider(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("dns", Base64.encodeToString(rawRequest, Base64.DEFAULT))
|
||||
.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()) {
|
||||
result = response.body().bytes();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
package org.itxtech.daedalus.provider;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.system.StructPollfd;
|
||||
import android.util.Log;
|
||||
import org.itxtech.daedalus.Daedalus;
|
||||
import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
import org.itxtech.daedalus.util.Logger;
|
||||
import org.minidns.dnsmessage.DnsMessage;
|
||||
import org.pcap4j.packet.IpPacket;
|
||||
import org.pcap4j.packet.IpSelector;
|
||||
import org.pcap4j.packet.UdpPacket;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
abstract public class HttpsProvider extends Provider {
|
||||
private static final String TAG = "HttpsProvider";
|
||||
|
||||
protected final WhqList whqList = new WhqList();
|
||||
|
||||
public HttpsProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) {
|
||||
super(descriptor, service);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void process() {
|
||||
try {
|
||||
FileDescriptor[] pipes = Os.pipe();
|
||||
mInterruptFd = pipes[0];
|
||||
mBlockFd = pipes[1];
|
||||
FileInputStream inputStream = new FileInputStream(descriptor.getFileDescriptor());
|
||||
FileOutputStream outputStream = new FileOutputStream(descriptor.getFileDescriptor());
|
||||
|
||||
byte[] packet = new byte[32767];
|
||||
while (running) {
|
||||
StructPollfd deviceFd = new StructPollfd();
|
||||
deviceFd.fd = inputStream.getFD();
|
||||
deviceFd.events = (short) OsConstants.POLLIN;
|
||||
StructPollfd blockFd = new StructPollfd();
|
||||
blockFd.fd = mBlockFd;
|
||||
blockFd.events = (short) (OsConstants.POLLHUP | OsConstants.POLLERR);
|
||||
|
||||
if (!deviceWrites.isEmpty())
|
||||
deviceFd.events |= (short) OsConstants.POLLOUT;
|
||||
|
||||
StructPollfd[] polls = new StructPollfd[2];
|
||||
polls[0] = deviceFd;
|
||||
polls[1] = blockFd;
|
||||
Os.poll(polls, -1);
|
||||
if (blockFd.revents != 0) {
|
||||
Log.i(TAG, "Told to stop VPN");
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean writeToDevice = false;
|
||||
Iterator<WaitingHttpsRequest> iterator = whqList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
WaitingHttpsRequest request = iterator.next();
|
||||
if (request.completed) {
|
||||
writeToDevice = true;
|
||||
handleDnsResponse(request.packet, request.result);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (writeToDevice) {
|
||||
Log.d(TAG, "Write to device");
|
||||
writeToDevice(outputStream);
|
||||
}
|
||||
if ((deviceFd.revents & OsConstants.POLLIN) != 0) {
|
||||
Log.d(TAG, "Read from device");
|
||||
readPacketFromDevice(inputStream, packet);
|
||||
}
|
||||
service.providerLoopCallback();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.logException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDnsRequest(byte[] packetData) {
|
||||
IpPacket parsedPacket;
|
||||
try {
|
||||
parsedPacket = (IpPacket) IpSelector.newPacket(packetData, 0, packetData.length);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(parsedPacket.getPayload() instanceof UdpPacket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress destAddr = parsedPacket.getHeader().getDstAddr();
|
||||
if (destAddr == null)
|
||||
return;
|
||||
String uri;
|
||||
try {
|
||||
uri = service.dnsServers.get(destAddr.getHostAddress());//https uri
|
||||
} catch (Exception e) {
|
||||
Logger.logException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
UdpPacket parsedUdp = (UdpPacket) parsedPacket.getPayload();
|
||||
|
||||
if (parsedUdp.getPayload() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] dnsRawData = (parsedUdp).getPayload().getRawData();
|
||||
DnsMessage dnsMsg;
|
||||
try {
|
||||
dnsMsg = new DnsMessage(dnsRawData);
|
||||
if (Daedalus.getPrefs().getBoolean("settings_debug_output", false)) {
|
||||
Logger.debug("DnsRequest: " + dnsMsg.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
if (dnsMsg.getQuestion() == null) {
|
||||
Log.i(TAG, "handleDnsRequest: Discarding DNS packet with no query " + dnsMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resolve(parsedPacket, dnsMsg) && uri != null) {
|
||||
sendRequestToServer(parsedPacket, dnsMsg, uri);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void sendRequestToServer(IpPacket parsedPacket, DnsMessage message, String uri);
|
||||
//uri example: 1.1.1.1:1234/dnsQuery. The specified provider will add https:// and parameters
|
||||
|
||||
public abstract static class WaitingHttpsRequest {
|
||||
public boolean completed = false;
|
||||
public byte[] result;
|
||||
public final IpPacket packet;
|
||||
|
||||
public WaitingHttpsRequest(IpPacket packet) {
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
public void add(WaitingHttpsRequest request) {
|
||||
list.add(request);
|
||||
request.doRequest();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Iterator<WaitingHttpsRequest> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,28 @@
|
||||
package org.itxtech.daedalus.provider;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.system.Os;
|
||||
import org.itxtech.daedalus.Daedalus;
|
||||
import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
import org.itxtech.daedalus.util.Logger;
|
||||
import org.itxtech.daedalus.util.RuleResolver;
|
||||
import org.minidns.dnsmessage.DnsMessage;
|
||||
import org.minidns.record.A;
|
||||
import org.minidns.record.AAAA;
|
||||
import org.minidns.record.Record;
|
||||
import org.pcap4j.packet.*;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Daedalus Project
|
||||
@ -15,10 +36,14 @@ import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
public abstract class Provider {
|
||||
ParcelFileDescriptor descriptor;
|
||||
DaedalusVpnService service;
|
||||
boolean running = false;
|
||||
static long dnsQueryTimes = 0;
|
||||
protected ParcelFileDescriptor descriptor;
|
||||
protected DaedalusVpnService service;
|
||||
protected boolean running = false;
|
||||
protected static long dnsQueryTimes = 0;
|
||||
|
||||
protected FileDescriptor mBlockFd = null;
|
||||
protected FileDescriptor mInterruptFd = null;
|
||||
protected final Queue<byte[]> deviceWrites = new LinkedList<>();
|
||||
|
||||
Provider(ParcelFileDescriptor descriptor, DaedalusVpnService service) {
|
||||
this.descriptor = descriptor;
|
||||
@ -40,5 +65,128 @@ public abstract class Provider {
|
||||
running = false;
|
||||
}
|
||||
|
||||
public abstract void stop();
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void stop() {
|
||||
try {
|
||||
if (mInterruptFd != null) {
|
||||
Os.close(mInterruptFd);
|
||||
}
|
||||
if (mBlockFd != null) {
|
||||
Os.close(mBlockFd);
|
||||
}
|
||||
if (this.descriptor != null) {
|
||||
this.descriptor.close();
|
||||
this.descriptor = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void queueDeviceWrite(IpPacket ipOutPacket) {
|
||||
dnsQueryTimes++;
|
||||
deviceWrites.add(ipOutPacket.getRawData());
|
||||
}
|
||||
|
||||
public boolean resolve(IpPacket parsedPacket, DnsMessage dnsMsg) {
|
||||
String dnsQueryName = dnsMsg.getQuestion().name.toString();
|
||||
|
||||
try {
|
||||
String response = RuleResolver.resolve(dnsQueryName, dnsMsg.getQuestion().type);
|
||||
if (response != null && dnsMsg.getQuestion().type == Record.TYPE.A) {
|
||||
Logger.info("Provider: Resolved " + dnsQueryName + " Local resolver response: " + response);
|
||||
DnsMessage.Builder builder = dnsMsg.asBuilder()
|
||||
.setQrFlag(true)
|
||||
.addAnswer(new Record<>(dnsQueryName, Record.TYPE.A, 1, 64,
|
||||
new A(Inet4Address.getByName(response).getAddress())));
|
||||
handleDnsResponse(parsedPacket, builder.build().toArray());
|
||||
return true;
|
||||
} else if (response != null && dnsMsg.getQuestion().type == Record.TYPE.AAAA) {
|
||||
Logger.info("Provider: Resolved " + dnsQueryName + " Local resolver response: " + response);
|
||||
DnsMessage.Builder builder = dnsMsg.asBuilder()
|
||||
.setQrFlag(true)
|
||||
.addAnswer(new Record<>(dnsQueryName, Record.TYPE.AAAA, 1, 64,
|
||||
new AAAA(Inet6Address.getByName(response).getAddress())));
|
||||
handleDnsResponse(parsedPacket, builder.build().toArray());
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.logException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a responsePayload from an upstream DNS server
|
||||
*
|
||||
* @param requestPacket The original request packet
|
||||
* @param responsePayload The payload of the response
|
||||
*/
|
||||
void handleDnsResponse(IpPacket requestPacket, byte[] responsePayload) {
|
||||
if (Daedalus.getPrefs().getBoolean("settings_debug_output", false)) {
|
||||
try {
|
||||
Logger.debug("DnsResponse: " + new DnsMessage(responsePayload).toString());
|
||||
} catch (IOException e) {
|
||||
Logger.logException(e);
|
||||
}
|
||||
}
|
||||
UdpPacket udpOutPacket = (UdpPacket) requestPacket.getPayload();
|
||||
UdpPacket.Builder payLoadBuilder = new UdpPacket.Builder(udpOutPacket)
|
||||
.srcPort(udpOutPacket.getHeader().getDstPort())
|
||||
.dstPort(udpOutPacket.getHeader().getSrcPort())
|
||||
.srcAddr(requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr(requestPacket.getHeader().getSrcAddr())
|
||||
.correctChecksumAtBuild(true)
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(
|
||||
new UnknownPacket.Builder()
|
||||
.rawData(responsePayload)
|
||||
);
|
||||
|
||||
|
||||
IpPacket ipOutPacket;
|
||||
if (requestPacket instanceof IpV4Packet) {
|
||||
ipOutPacket = new IpV4Packet.Builder((IpV4Packet) requestPacket)
|
||||
.srcAddr((Inet4Address) requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr((Inet4Address) requestPacket.getHeader().getSrcAddr())
|
||||
.correctChecksumAtBuild(true)
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(payLoadBuilder)
|
||||
.build();
|
||||
|
||||
} else {
|
||||
ipOutPacket = new IpV6Packet.Builder((IpV6Packet) requestPacket)
|
||||
.srcAddr((Inet6Address) requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr((Inet6Address) requestPacket.getHeader().getSrcAddr())
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(payLoadBuilder)
|
||||
.build();
|
||||
}
|
||||
|
||||
queueDeviceWrite(ipOutPacket);
|
||||
}
|
||||
|
||||
protected void writeToDevice(FileOutputStream outFd) throws DaedalusVpnService.VpnNetworkException {
|
||||
try {
|
||||
outFd.write(deviceWrites.poll());
|
||||
} catch (IOException e) {
|
||||
throw new DaedalusVpnService.VpnNetworkException("Outgoing VPN output stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
protected void readPacketFromDevice(FileInputStream inputStream, byte[] packet) throws DaedalusVpnService.VpnNetworkException {
|
||||
// Read the outgoing packet from the input stream.
|
||||
int length;
|
||||
try {
|
||||
length = inputStream.read(packet);
|
||||
} catch (IOException e) {
|
||||
throw new DaedalusVpnService.VpnNetworkException("Cannot read from device", e);
|
||||
}
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
final byte[] readPacket = Arrays.copyOfRange(packet, 0, length);
|
||||
handleDnsRequest(readPacket);
|
||||
}
|
||||
|
||||
protected abstract void handleDnsRequest(byte[] packetData) throws DaedalusVpnService.VpnNetworkException;
|
||||
}
|
@ -5,21 +5,34 @@ import org.itxtech.daedalus.Daedalus;
|
||||
import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
|
||||
/**
|
||||
* @author PeratX
|
||||
* 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 ProviderPicker {
|
||||
public static final int DNS_QUERY_METHOD_UDP = 0;
|
||||
public static final int DNS_QUERY_METHOD_TCP = 1;
|
||||
public static final int DNS_QUERY_METHOD_HTTPS = 2;
|
||||
public static final int DNS_QUERY_METHOD_TLS = 2;
|
||||
public static final int DNS_QUERY_METHOD_HTTPS_IETF = 3;
|
||||
public static final int DNS_QUERY_METHOD_HTTPS_JSON = 4;
|
||||
|
||||
public static Provider getProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) {
|
||||
switch (Daedalus.getPrefs().getInt("settings_dns_query_method", DNS_QUERY_METHOD_UDP)) {
|
||||
switch (Integer.valueOf(Daedalus.getPrefs().getString("settings_dns_query_method", "0"))) {
|
||||
case DNS_QUERY_METHOD_UDP:
|
||||
return new UdpProvider(descriptor, service);
|
||||
case DNS_QUERY_METHOD_TCP:
|
||||
return new TcpProvider(descriptor, service);
|
||||
case DNS_QUERY_METHOD_HTTPS:
|
||||
//TODO
|
||||
case DNS_QUERY_METHOD_HTTPS_IETF:
|
||||
return new HttpsIetfProvider(descriptor, service);
|
||||
case DNS_QUERY_METHOD_HTTPS_JSON:
|
||||
break;
|
||||
case DNS_QUERY_METHOD_TLS:
|
||||
break;
|
||||
}
|
||||
return new UdpProvider(descriptor, service);
|
||||
|
@ -47,7 +47,6 @@ public class TcpProvider extends UdpProvider {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void process() {
|
||||
try {
|
||||
Log.d(TAG, "Starting advanced DNS proxy.");
|
||||
FileDescriptor[] pipes = Os.pipe();
|
||||
mInterruptFd = pipes[0];
|
||||
mBlockFd = pipes[1];
|
||||
|
@ -11,23 +11,21 @@ import android.util.Log;
|
||||
import org.itxtech.daedalus.Daedalus;
|
||||
import org.itxtech.daedalus.service.DaedalusVpnService;
|
||||
import org.itxtech.daedalus.util.Logger;
|
||||
import org.itxtech.daedalus.util.RuleResolver;
|
||||
import org.itxtech.daedalus.util.server.DNSServerHelper;
|
||||
import org.minidns.dnsmessage.DnsMessage;
|
||||
import org.minidns.record.A;
|
||||
import org.minidns.record.AAAA;
|
||||
import org.minidns.record.Record;
|
||||
import org.pcap4j.packet.*;
|
||||
import org.pcap4j.packet.IpPacket;
|
||||
import org.pcap4j.packet.IpSelector;
|
||||
import org.pcap4j.packet.UdpPacket;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Arrays;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Daedalus Project
|
||||
@ -44,40 +42,14 @@ public class UdpProvider extends Provider {
|
||||
private static final String TAG = "UdpProvider";
|
||||
|
||||
private final WospList dnsIn = new WospList();
|
||||
FileDescriptor mBlockFd = null;
|
||||
FileDescriptor mInterruptFd = null;
|
||||
final Queue<byte[]> deviceWrites = new LinkedList<>();
|
||||
|
||||
public UdpProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) {
|
||||
super(descriptor, service);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void stop() {
|
||||
try {
|
||||
if (mInterruptFd != null) {
|
||||
Os.close(mInterruptFd);
|
||||
}
|
||||
if (mBlockFd != null) {
|
||||
Os.close(mBlockFd);
|
||||
}
|
||||
if (this.descriptor != null) {
|
||||
this.descriptor.close();
|
||||
this.descriptor = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void queueDeviceWrite(IpPacket ipOutPacket) {
|
||||
dnsQueryTimes++;
|
||||
deviceWrites.add(ipOutPacket.getRawData());
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public void process() {
|
||||
try {
|
||||
Log.d(TAG, "Starting advanced DNS proxy.");
|
||||
FileDescriptor[] pipes = Os.pipe();
|
||||
mInterruptFd = pipes[0];
|
||||
mBlockFd = pipes[1];
|
||||
@ -149,35 +121,6 @@ public class UdpProvider extends Provider {
|
||||
}
|
||||
}
|
||||
|
||||
void writeToDevice(FileOutputStream outFd) throws DaedalusVpnService.VpnNetworkException {
|
||||
try {
|
||||
outFd.write(deviceWrites.poll());
|
||||
} catch (IOException e) {
|
||||
throw new DaedalusVpnService.VpnNetworkException("Outgoing VPN output stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
void readPacketFromDevice(FileInputStream inputStream, byte[] packet) throws DaedalusVpnService.VpnNetworkException {
|
||||
// Read the outgoing packet from the input stream.
|
||||
int length;
|
||||
|
||||
try {
|
||||
length = inputStream.read(packet);
|
||||
} catch (IOException e) {
|
||||
throw new DaedalusVpnService.VpnNetworkException("Cannot read from device", e);
|
||||
}
|
||||
|
||||
|
||||
if (length == 0) {
|
||||
Log.w(TAG, "Got empty packet!");
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] readPacket = Arrays.copyOfRange(packet, 0, length);
|
||||
|
||||
handleDnsRequest(readPacket);
|
||||
}
|
||||
|
||||
void forwardPacket(DatagramPacket outPacket, IpPacket parsedPacket) throws DaedalusVpnService.VpnNetworkException {
|
||||
DatagramSocket dnsSocket;
|
||||
try {
|
||||
@ -210,64 +153,14 @@ public class UdpProvider extends Provider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a responsePayload from an upstream DNS server
|
||||
*
|
||||
* @param requestPacket The original request packet
|
||||
* @param responsePayload The payload of the response
|
||||
*/
|
||||
void handleDnsResponse(IpPacket requestPacket, byte[] responsePayload) {
|
||||
if (Daedalus.getPrefs().getBoolean("settings_debug_output", false)) {
|
||||
try {
|
||||
Logger.debug(new DnsMessage(responsePayload).toString());
|
||||
} catch (IOException e) {
|
||||
Logger.logException(e);
|
||||
}
|
||||
}
|
||||
UdpPacket udpOutPacket = (UdpPacket) requestPacket.getPayload();
|
||||
UdpPacket.Builder payLoadBuilder = new UdpPacket.Builder(udpOutPacket)
|
||||
.srcPort(udpOutPacket.getHeader().getDstPort())
|
||||
.dstPort(udpOutPacket.getHeader().getSrcPort())
|
||||
.srcAddr(requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr(requestPacket.getHeader().getSrcAddr())
|
||||
.correctChecksumAtBuild(true)
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(
|
||||
new UnknownPacket.Builder()
|
||||
.rawData(responsePayload)
|
||||
);
|
||||
|
||||
|
||||
IpPacket ipOutPacket;
|
||||
if (requestPacket instanceof IpV4Packet) {
|
||||
ipOutPacket = new IpV4Packet.Builder((IpV4Packet) requestPacket)
|
||||
.srcAddr((Inet4Address) requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr((Inet4Address) requestPacket.getHeader().getSrcAddr())
|
||||
.correctChecksumAtBuild(true)
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(payLoadBuilder)
|
||||
.build();
|
||||
|
||||
} else {
|
||||
ipOutPacket = new IpV6Packet.Builder((IpV6Packet) requestPacket)
|
||||
.srcAddr((Inet6Address) requestPacket.getHeader().getDstAddr())
|
||||
.dstAddr((Inet6Address) requestPacket.getHeader().getSrcAddr())
|
||||
.correctLengthAtBuild(true)
|
||||
.payloadBuilder(payLoadBuilder)
|
||||
.build();
|
||||
}
|
||||
|
||||
queueDeviceWrite(ipOutPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a DNS request, by either blocking it or forwarding it to the remote location.
|
||||
*
|
||||
* @param packetData The packet data to read
|
||||
* @throws DaedalusVpnService.VpnNetworkException If some network error occurred
|
||||
*/
|
||||
private void handleDnsRequest(byte[] packetData) throws DaedalusVpnService.VpnNetworkException {
|
||||
@Override
|
||||
protected void handleDnsRequest(byte[] packetData) throws DaedalusVpnService.VpnNetworkException {
|
||||
|
||||
IpPacket parsedPacket;
|
||||
try {
|
||||
@ -312,7 +205,7 @@ public class UdpProvider extends Provider {
|
||||
try {
|
||||
dnsMsg = new DnsMessage(dnsRawData);
|
||||
if (Daedalus.getPrefs().getBoolean("settings_debug_output", false)) {
|
||||
Logger.debug(dnsMsg.toString());
|
||||
Logger.debug("DnsRequest: " + dnsMsg.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "handleDnsRequest: Discarding non-DNS or invalid packet", e);
|
||||
@ -322,32 +215,11 @@ public class UdpProvider extends Provider {
|
||||
Log.i(TAG, "handleDnsRequest: Discarding DNS packet with no query " + dnsMsg);
|
||||
return;
|
||||
}
|
||||
String dnsQueryName = dnsMsg.getQuestion().name.toString();
|
||||
|
||||
try {
|
||||
String response = RuleResolver.resolve(dnsQueryName, dnsMsg.getQuestion().type);
|
||||
if (response != null && dnsMsg.getQuestion().type == Record.TYPE.A) {
|
||||
Logger.info("Provider: Resolved " + dnsQueryName + " Local resolver response: " + response);
|
||||
DnsMessage.Builder builder = dnsMsg.asBuilder()
|
||||
.setQrFlag(true)
|
||||
.addAnswer(new Record<>(dnsQueryName, Record.TYPE.A, 1, 64,
|
||||
new A(Inet4Address.getByName(response).getAddress())));
|
||||
handleDnsResponse(parsedPacket, builder.build().toArray());
|
||||
} else if (response != null && dnsMsg.getQuestion().type == Record.TYPE.AAAA) {
|
||||
Logger.info("Provider: Resolved " + dnsQueryName + " Local resolver response: " + response);
|
||||
DnsMessage.Builder builder = dnsMsg.asBuilder()
|
||||
.setQrFlag(true)
|
||||
.addAnswer(new Record<>(dnsQueryName, Record.TYPE.AAAA, 1, 64,
|
||||
new AAAA(Inet6Address.getByName(response).getAddress())));
|
||||
handleDnsResponse(parsedPacket, builder.build().toArray());
|
||||
} else {
|
||||
Logger.info("Provider: Resolving " + dnsQueryName + " Type: " + dnsMsg.getQuestion().type.name() + " Sending to " + destAddr);
|
||||
DatagramPacket outPacket = new DatagramPacket(dnsRawData, 0, dnsRawData.length, destAddr,
|
||||
DNSServerHelper.getPortOrDefault(destAddr, parsedUdp.getHeader().getDstPort().valueAsInt()));
|
||||
forwardPacket(outPacket, parsedPacket);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.logException(e);
|
||||
if (!resolve(parsedPacket, dnsMsg)) {
|
||||
DatagramPacket outPacket = new DatagramPacket(dnsRawData, 0, dnsRawData.length, destAddr,
|
||||
DNSServerHelper.getPortOrDefault(destAddr, parsedUdp.getHeader().getDstPort().valueAsInt()));
|
||||
forwardPacket(outPacket, parsedPacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,6 @@ import org.itxtech.daedalus.R;
|
||||
import org.itxtech.daedalus.activity.MainActivity;
|
||||
import org.itxtech.daedalus.provider.Provider;
|
||||
import org.itxtech.daedalus.provider.ProviderPicker;
|
||||
import org.itxtech.daedalus.provider.TcpProvider;
|
||||
import org.itxtech.daedalus.provider.UdpProvider;
|
||||
import org.itxtech.daedalus.receiver.StatusBarBroadcastReceiver;
|
||||
import org.itxtech.daedalus.util.Logger;
|
||||
import org.itxtech.daedalus.util.RuleResolver;
|
||||
@ -191,9 +189,16 @@ public class DaedalusVpnService extends VpnService implements Runnable {
|
||||
stopThread();
|
||||
}
|
||||
|
||||
private InetAddress addDnsServer(Builder builder, String format, byte[] ipv6Template, InetAddress address) throws UnknownHostException {
|
||||
private InetAddress addDnsServer(Builder builder, String format, byte[] ipv6Template, String addr) throws UnknownHostException {
|
||||
int size = dnsServers.size();
|
||||
size++;
|
||||
if (addr.contains("/")) {//https uri
|
||||
String alias = String.format(format, size + 1);
|
||||
dnsServers.put(alias, addr);
|
||||
builder.addRoute(alias, 32);
|
||||
return InetAddress.getByName(alias);
|
||||
}
|
||||
InetAddress address = InetAddress.getByName(addr);
|
||||
if (address instanceof Inet6Address && ipv6Template == null) {
|
||||
Log.i(TAG, "addDnsServer: Ignoring DNS server " + address);
|
||||
} else if (address instanceof Inet4Address) {
|
||||
@ -253,8 +258,8 @@ public class DaedalusVpnService extends VpnService implements Runnable {
|
||||
InetAddress aliasSecondary;
|
||||
if (advanced) {
|
||||
dnsServers = new HashMap<>();
|
||||
aliasPrimary = addDnsServer(builder, format, ipv6Template, InetAddress.getByName(primaryServer));
|
||||
aliasSecondary = addDnsServer(builder, format, ipv6Template, InetAddress.getByName(secondaryServer));
|
||||
aliasPrimary = addDnsServer(builder, format, ipv6Template, primaryServer);
|
||||
aliasSecondary = addDnsServer(builder, format, ipv6Template, secondaryServer);
|
||||
} else {
|
||||
aliasPrimary = InetAddress.getByName(primaryServer);
|
||||
aliasSecondary = InetAddress.getByName(secondaryServer);
|
||||
|
@ -1,14 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer-array name="dns_query_methods">
|
||||
<string-array name="dns_query_method_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
<string-array name="dns_query_method_values">
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
<string-array name="dns_query_methods">
|
||||
<item>@string/settings_dns_udp</item>
|
||||
<item>@string/settings_dns_tcp</item>
|
||||
<item>@string/settings_dns_https</item>
|
||||
<item>@string/settings_dns_tls</item>
|
||||
<item>@string/settings_dns_https_ietf</item>
|
||||
<item>@string/settings_dns_https_json</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
|
@ -66,9 +66,11 @@
|
||||
<string name="settings_dark_theme">Use Dark Theme</string>
|
||||
<string name="settings_dns_query_method">DNS Query Method</string>
|
||||
|
||||
<string name="settings_dns_https">HTTPS</string>
|
||||
<string name="settings_dns_tcp">TCP</string>
|
||||
<string name="settings_dns_udp">UDP</string>
|
||||
<string name="settings_dns_tls">TLS</string>
|
||||
<string name="settings_dns_https_ietf">HTTPS (IETF draft)</string>
|
||||
<string name="settings_dns_https_json">HTTPS (Google JSON)</string>
|
||||
|
||||
<string name="settings_rule_name">Rule Name</string>
|
||||
<string name="settings_rule_type">Rule Type</string>
|
||||
|
@ -76,6 +76,7 @@
|
||||
android:dialogTitle="@string/settings_dns_query_method"
|
||||
android:entries="@array/dns_query_methods"
|
||||
android:entryValues="@array/dns_query_method_values"
|
||||
android:defaultValue="0"
|
||||
android:enabled="false"/>
|
||||
<SwitchPreference
|
||||
android:key="settings_debug_output"
|
||||
|
Loading…
Reference in New Issue
Block a user