diff --git a/app/src/main/java/org/itxtech/daedalus/Daedalus.java b/app/src/main/java/org/itxtech/daedalus/Daedalus.java index 2b3aa94..b3aaf21 100644 --- a/app/src/main/java/org/itxtech/daedalus/Daedalus.java +++ b/app/src/main/java/org/itxtech/daedalus/Daedalus.java @@ -38,7 +38,8 @@ import java.util.List; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class Daedalus extends Application { private static final String SHORTCUT_ID_ACTIVATE = "shortcut_activate"; diff --git a/app/src/main/java/org/itxtech/daedalus/activity/DnsServerConfigActivity.java b/app/src/main/java/org/itxtech/daedalus/activity/DnsServerConfigActivity.java index 097f533..8eedada 100644 --- a/app/src/main/java/org/itxtech/daedalus/activity/DnsServerConfigActivity.java +++ b/app/src/main/java/org/itxtech/daedalus/activity/DnsServerConfigActivity.java @@ -21,7 +21,8 @@ import org.itxtech.daedalus.fragment.DnsServerConfigFragment; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsServerConfigActivity extends AppCompatActivity { public static final String LAUNCH_ACTION_CUSTOM_DNS_SERVER_ID = "org.itxtech.daedalus.activity.DnsServerConfigActivity.LAUNCH_ACTION_CUSTOM_DNS_SERVER_ID"; diff --git a/app/src/main/java/org/itxtech/daedalus/activity/MainActivity.java b/app/src/main/java/org/itxtech/daedalus/activity/MainActivity.java index 3429f98..bc8108d 100644 --- a/app/src/main/java/org/itxtech/daedalus/activity/MainActivity.java +++ b/app/src/main/java/org/itxtech/daedalus/activity/MainActivity.java @@ -34,7 +34,8 @@ import org.itxtech.daedalus.fragment.*; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { public static final String LAUNCH_ACTION = "org.itxtech.daedalus.activity.MainActivity.LAUNCH_ACTION"; diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/AboutFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/AboutFragment.java index 184a70c..60733ff 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/AboutFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/AboutFragment.java @@ -25,7 +25,8 @@ import java.util.Locale; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class AboutFragment extends Fragment { private WebView mWebView = null; diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/DnsServerConfigFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/DnsServerConfigFragment.java index 43d0435..ab90a1c 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/DnsServerConfigFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/DnsServerConfigFragment.java @@ -25,7 +25,8 @@ import org.itxtech.daedalus.util.DnsServer; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsServerConfigFragment extends PreferenceFragment implements Toolbar.OnMenuItemClickListener { private Intent intent = null; diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/DnsServersFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/DnsServersFragment.java index 99664ed..03b29ea 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/DnsServersFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/DnsServersFragment.java @@ -24,7 +24,8 @@ import org.itxtech.daedalus.util.CustomDnsServer; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsServersFragment extends Fragment { private DnsServersFragment.DnsServerAdapter adapter; diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/DnsTestFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/DnsTestFragment.java index 03b7f45..a2fe919 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/DnsTestFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/DnsTestFragment.java @@ -37,7 +37,8 @@ import java.util.Set; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsTestFragment extends Fragment { private static final String TAG = "DServerTest"; diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/HostsFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/HostsFragment.java index 68c7ac3..0f0c196 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/HostsFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/HostsFragment.java @@ -30,7 +30,8 @@ import java.util.Date; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class HostsFragment extends Fragment { diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/MainFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/MainFragment.java index 658270e..be0c052 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/MainFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/MainFragment.java @@ -26,7 +26,8 @@ import org.itxtech.daedalus.util.DnsServerHelper; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class MainFragment extends Fragment { diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/SettingsFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/SettingsFragment.java index f0626c7..4f5183f 100644 --- a/app/src/main/java/org/itxtech/daedalus/fragment/SettingsFragment.java +++ b/app/src/main/java/org/itxtech/daedalus/fragment/SettingsFragment.java @@ -21,7 +21,8 @@ import org.itxtech.daedalus.util.DnsServerHelper; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class SettingsFragment extends PreferenceFragment { private View view = null; diff --git a/app/src/main/java/org/itxtech/daedalus/provider/DnsProvider.java b/app/src/main/java/org/itxtech/daedalus/provider/DnsProvider.java new file mode 100644 index 0000000..12e0178 --- /dev/null +++ b/app/src/main/java/org/itxtech/daedalus/provider/DnsProvider.java @@ -0,0 +1,44 @@ +package org.itxtech.daedalus.provider; + +import android.os.ParcelFileDescriptor; +import org.itxtech.daedalus.service.DaedalusVpnService; + +/** + * Daedalus Project + * + * @author iTXTech + * @link https://itxtech.org + *

+ * 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 abstract class DnsProvider { + ParcelFileDescriptor descriptor; + DaedalusVpnService service; + boolean running = false; + static long dnsQueryTimes = 0; + + DnsProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) { + this.descriptor = descriptor; + this.service = service; + dnsQueryTimes = 0; + } + + public final long getDnsQueryTimes() { + return dnsQueryTimes; + } + + public abstract void process(); + + public final void start() { + running = true; + } + + public final void shutdown() { + running = false; + } + + public abstract void stop(); +} \ No newline at end of file diff --git a/app/src/main/java/org/itxtech/daedalus/provider/TcpDnsProvider.java b/app/src/main/java/org/itxtech/daedalus/provider/TcpDnsProvider.java new file mode 100644 index 0000000..ca5af94 --- /dev/null +++ b/app/src/main/java/org/itxtech/daedalus/provider/TcpDnsProvider.java @@ -0,0 +1,264 @@ +package org.itxtech.daedalus.provider; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructPollfd; +import android.util.Log; +import de.measite.minidns.DNSMessage; +import org.itxtech.daedalus.service.DaedalusVpnService; +import org.itxtech.daedalus.util.DnsServerHelper; +import org.pcap4j.packet.*; + +import java.io.*; +import java.net.*; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * @author PeratX + */ +public class TcpDnsProvider extends UdpDnsProvider { + + private static final String TAG = "TcpDnsProvider"; + + private final TcpDnsProvider.WospList dnsIn = new TcpDnsProvider.WospList(); + + public TcpDnsProvider(ParcelFileDescriptor descriptor, DaedalusVpnService service) { + super(descriptor, service); + } + + @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]; + 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 + dnsIn.size()]; + polls[0] = deviceFd; + polls[1] = blockFd; + { + int i = -1; + for (TcpDnsProvider.WaitingOnSocketPacket wosp : dnsIn) { + i++; + StructPollfd pollFd = polls[2 + i] = new StructPollfd(); + pollFd.fd = ParcelFileDescriptor.fromSocket(wosp.socket).getFileDescriptor(); + pollFd.events = (short) OsConstants.POLLIN; + } + } + + Log.d(TAG, "doOne: Polling " + polls.length + " file descriptors"); + Os.poll(polls, -1); + if (blockFd.revents != 0) { + Log.i(TAG, "Told to stop VPN"); + running = false; + return; + } + + // Need to do this before reading from the device, otherwise a new insertion there could + // invalidate one of the sockets we want to read from either due to size or time out + // constraints + { + int i = -1; + Iterator iter = dnsIn.iterator(); + while (iter.hasNext()) { + i++; + TcpDnsProvider.WaitingOnSocketPacket wosp = iter.next(); + if ((polls[i + 2].revents & OsConstants.POLLIN) != 0) { + Log.d(TAG, "Read from TCP DNS socket" + wosp.socket); + iter.remove(); + handleRawDnsResponse(wosp.packet, wosp.socket); + wosp.socket.close(); + } + } + } + if ((deviceFd.revents & OsConstants.POLLOUT) != 0) { + Log.d(TAG, "Write to device"); + writeToDevice(outputStream); + } + if ((deviceFd.revents & OsConstants.POLLIN) != 0) { + Log.d(TAG, "Read from device"); + readPacketFromDevice(inputStream, packet); + } + + checkCache(); + + service.providerLoopCallback(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private byte[] processUdpPacket(DatagramPacket outPacket, IpPacket parsedPacket) { + if (parsedPacket == null) { + return new byte[0]; + } + return outPacket.getData(); + } + + void forwardPacket(DatagramPacket outPacket, IpPacket parsedPacket) throws DaedalusVpnService.VpnNetworkException { + Socket dnsSocket; + try { + // Packets to be sent to the real DNS server will need to be protected from the VPN + dnsSocket = SocketChannel.open().socket(); + + Log.d(TAG, String.valueOf(service.protect(dnsSocket))); + + SocketAddress address = new InetSocketAddress(outPacket.getAddress(), DnsServerHelper.getPortOrDefault(outPacket.getAddress(), outPacket.getPort())); + dnsSocket.connect(address, 5000); + dnsSocket.setSoTimeout(5000); + Log.d(TAG, "Sending"); + DataOutputStream dos = new DataOutputStream(dnsSocket.getOutputStream()); + byte[] packet = processUdpPacket(outPacket, parsedPacket); + dos.writeShort(packet.length); + dos.write(packet); + dos.flush(); + + if (parsedPacket != null) { + dnsIn.add(new TcpDnsProvider.WaitingOnSocketPacket(dnsSocket, parsedPacket)); + } else { + dnsSocket.close(); + } + } catch (IOException e) { + if (e.getCause() instanceof ErrnoException) { + ErrnoException errnoExc = (ErrnoException) e.getCause(); + if ((errnoExc.errno == OsConstants.ENETUNREACH) || (errnoExc.errno == OsConstants.EPERM)) { + throw new DaedalusVpnService.VpnNetworkException("Cannot send message:", e); + } + } + Log.w(TAG, "handleDnsRequest: Could not send packet to upstream", e); + } + } + + private void handleRawDnsResponse(IpPacket parsedPacket, Socket dnsSocket) throws IOException, IllegalRawDataException { + if (!dnsSocket.isClosed()) { + DataInputStream stream = new DataInputStream(dnsSocket.getInputStream()); + int length = stream.readUnsignedShort(); + Log.d(TAG, "Reading length: " + String.valueOf(length)); + byte[] data = new byte[length]; + stream.read(data); + dnsSocket.close(); + handleDnsResponse(parsedPacket, data); + } + } + + + /** + * Handles a responsePayload from an upstream DNS server + * + * @param requestPacket The original request packet + * @param responsePayload The payload of the response + */ + private void handleDnsResponse(IpPacket requestPacket, byte[] responsePayload) throws IllegalRawDataException, IOException { + DNSMessage message = new DNSMessage(responsePayload); + Log.d(TAG, message.toString()); + + 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); + } + + /** + * Helper class holding a socket, the packet we are waiting the answer for, and a time + */ + private static class WaitingOnSocketPacket { + final Socket socket; + final IpPacket packet; + private final long time; + + WaitingOnSocketPacket(Socket socket, IpPacket packet) { + this.socket = socket; + this.packet = packet; + this.time = System.currentTimeMillis(); + } + + long ageSeconds() { + return (System.currentTimeMillis() - time) / 1000; + } + } + + /** + * Queue of WaitingOnSocketPacket, bound on time and space. + */ + private static class WospList implements Iterable { + private final LinkedList list = new LinkedList<>(); + + void add(TcpDnsProvider.WaitingOnSocketPacket wosp) { + try { + if (list.size() > 1024) { + Log.d(TAG, "Dropping socket due to space constraints: " + list.element().socket); + list.element().socket.close(); + list.remove(); + } + while (!list.isEmpty() && list.element().ageSeconds() > 10) { + Log.d(TAG, "Timeout on socket " + list.element().socket); + list.element().socket.close(); + list.remove(); + } + list.add(wosp); + } catch (Exception ignored) { + } + } + + public Iterator iterator() { + return list.iterator(); + } + + int size() { + return list.size(); + } + + } +} diff --git a/app/src/main/java/org/itxtech/daedalus/provider/UdpDnsProvider.java b/app/src/main/java/org/itxtech/daedalus/provider/UdpDnsProvider.java new file mode 100644 index 0000000..7d4b4b9 --- /dev/null +++ b/app/src/main/java/org/itxtech/daedalus/provider/UdpDnsProvider.java @@ -0,0 +1,414 @@ +package org.itxtech.daedalus.provider; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructPollfd; +import android.util.Log; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Record; +import de.measite.minidns.record.A; +import org.itxtech.daedalus.service.DaedalusVpnService; +import org.itxtech.daedalus.util.DnsServerHelper; +import org.itxtech.daedalus.util.HostsResolver; +import org.pcap4j.packet.*; +import org.pcap4j.packet.factory.PacketFactoryPropertiesLoader; +import org.pcap4j.util.PropertiesLoader; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.*; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Daedalus Project + * + * @author iTXTech + * @link https://itxtech.org + *

+ * 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 UdpDnsProvider extends DnsProvider { + private static final String TAG = "UdpDnsProvider"; + + private final WospList dnsIn = new WospList(); + FileDescriptor mBlockFd = null; + FileDescriptor mInterruptFd = null; + final Queue deviceWrites = new LinkedList<>(); + + /** + * Number of iterations since we last cleared the pcap4j cache + */ + private int pcap4jFactoryClearCacheCounter = 0; + + public UdpDnsProvider(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) { + } + } + + void queueDeviceWrite(IpPacket ipOutPacket) { + dnsQueryTimes++; + Log.i(TAG, "QT " + 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]; + 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 + dnsIn.size()]; + polls[0] = deviceFd; + polls[1] = blockFd; + { + int i = -1; + for (WaitingOnSocketPacket wosp : dnsIn) { + i++; + StructPollfd pollFd = polls[2 + i] = new StructPollfd(); + pollFd.fd = ParcelFileDescriptor.fromDatagramSocket(wosp.socket).getFileDescriptor(); + pollFd.events = (short) OsConstants.POLLIN; + } + } + + Log.d(TAG, "doOne: Polling " + polls.length + " file descriptors"); + Os.poll(polls, -1); + if (blockFd.revents != 0) { + Log.i(TAG, "Told to stop VPN"); + running = false; + return; + } + + // Need to do this before reading from the device, otherwise a new insertion there could + // invalidate one of the sockets we want to read from either due to size or time out + // constraints + { + int i = -1; + Iterator iter = dnsIn.iterator(); + while (iter.hasNext()) { + i++; + WaitingOnSocketPacket wosp = iter.next(); + if ((polls[i + 2].revents & OsConstants.POLLIN) != 0) { + Log.d(TAG, "Read from UDP DNS socket" + wosp.socket); + iter.remove(); + handleRawDnsResponse(wosp.packet, wosp.socket); + wosp.socket.close(); + } + } + } + if ((deviceFd.revents & OsConstants.POLLOUT) != 0) { + Log.d(TAG, "Write to device"); + writeToDevice(outputStream); + } + if ((deviceFd.revents & OsConstants.POLLIN) != 0) { + Log.d(TAG, "Read from device"); + readPacketFromDevice(inputStream, packet); + } + + checkCache(); + service.providerLoopCallback(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + void checkCache() { + // pcap4j has some sort of properties cache in the packet factory. This cache leaks, so + // we need to clean it up. + if (++pcap4jFactoryClearCacheCounter % 1024 == 0) { + try { + PacketFactoryPropertiesLoader l = PacketFactoryPropertiesLoader.getInstance(); + Field field = l.getClass().getDeclaredField("loader"); + field.setAccessible(true); + PropertiesLoader loader = (PropertiesLoader) field.get(l); + Log.d(TAG, "Cleaning cache"); + loader.clearCache(); + } catch (NoSuchFieldException e) { + Log.e(TAG, "Cannot find declared loader field", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Cannot get declared loader field", e); + } + } + } + + void writeToDevice(FileOutputStream outFd) throws DaedalusVpnService.VpnNetworkException { + try { + outFd.write(deviceWrites.poll()); + } catch (IOException e) { + // TODO: Make this more specific, only for: "File descriptor closed" + throw new DaedalusVpnService.VpnNetworkException("Outgoing VPN output stream closed"); + } + } + + void readPacketFromDevice(FileInputStream inputStream, byte[] packet) throws DaedalusVpnService.VpnNetworkException, SocketException { + // 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) { + // TODO: Possibly change to exception + 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 { + // Packets to be sent to the real DNS server will need to be protected from the VPN + dnsSocket = new DatagramSocket(); + + service.protect(dnsSocket); + + dnsSocket.send(outPacket); + + if (parsedPacket != null) { + dnsIn.add(new WaitingOnSocketPacket(dnsSocket, parsedPacket)); + } else { + dnsSocket.close(); + } + } catch (IOException e) { + if (e.getCause() instanceof ErrnoException) { + ErrnoException errnoExc = (ErrnoException) e.getCause(); + if ((errnoExc.errno == OsConstants.ENETUNREACH) || (errnoExc.errno == OsConstants.EPERM)) { + throw new DaedalusVpnService.VpnNetworkException("Cannot send message:", e); + } + } + Log.w(TAG, "handleDnsRequest: Could not send packet to upstream", e); + } + } + + private void handleRawDnsResponse(IpPacket parsedPacket, DatagramSocket dnsSocket) throws IOException { + byte[] datagramData = new byte[1024]; + DatagramPacket replyPacket = new DatagramPacket(datagramData, datagramData.length); + dnsSocket.receive(replyPacket); + handleDnsResponse(parsedPacket, datagramData); + } + + + /** + * Handles a responsePayload from an upstream DNS server + * + * @param requestPacket The original request packet + * @param responsePayload The payload of the response + */ + private void handleDnsResponse(IpPacket requestPacket, byte[] responsePayload) { + 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 { + + IpPacket parsedPacket; + try { + parsedPacket = (IpPacket) IpSelector.newPacket(packetData, 0, packetData.length); + //TODO: get rid of pcap4j + } catch (Exception e) { + Log.i(TAG, "handleDnsRequest: Discarding invalid IP packet", e); + return; + } + + if (!(parsedPacket.getPayload() instanceof UdpPacket)) { + Log.i(TAG, "handleDnsRequest: Discarding unknown packet type " + parsedPacket.getPayload()); + return; + } + + InetAddress destAddr = parsedPacket.getHeader().getDstAddr(); + if (destAddr == null) + return; + + UdpPacket parsedUdp = (UdpPacket) parsedPacket.getPayload(); + + + if (parsedUdp.getPayload() == null) { + Log.i(TAG, "handleDnsRequest: Sending UDP packet without payload: " + parsedUdp); + + // Let's be nice to Firefox. Firefox uses an empty UDP packet to + // the gateway to reduce the RTT. For further details, please see + // https://bugzilla.mozilla.org/show_bug.cgi?id=888268 + DatagramPacket outPacket = new DatagramPacket(new byte[0], 0, 0, destAddr, + DnsServerHelper.getPortOrDefault(destAddr, parsedUdp.getHeader().getDstPort().valueAsInt())); + forwardPacket(outPacket, null); + return; + } + + byte[] dnsRawData = (parsedUdp).getPayload().getRawData(); + DNSMessage dnsMsg; + try { + dnsMsg = new DNSMessage(dnsRawData); + } catch (IOException e) { + Log.i(TAG, "handleDnsRequest: Discarding non-DNS or invalid packet", e); + return; + } + if (dnsMsg.getQuestion() == null) { + Log.i(TAG, "handleDnsRequest: Discarding DNS packet with no query " + dnsMsg); + return; + } + String dnsQueryName = dnsMsg.getQuestion().name.toString(); + + try { + if (HostsResolver.canResolve(dnsQueryName)) { + String response = HostsResolver.resolve(dnsQueryName); + Log.i(TAG, "handleDnsRequest: DNS Name " + dnsQueryName + " address " + response + ", using local hosts to resolve."); + DNSMessage.Builder builder = dnsMsg.asBuilder(); + int[] ip = new int[4]; + byte i = 0; + for (String block : response.split("\\.")) { + ip[i] = Integer.parseInt(block); + i++; + } + builder.addAnswer(new Record<>(dnsQueryName, Record.TYPE.getType(A.class), 1, 64, new A(ip[0], ip[1], ip[2], ip[3]))); + handleDnsResponse(parsedPacket, builder.build().toArray()); + } else { + Log.i(TAG, "handleDnsRequest: DNS Name " + dnsQueryName + " , 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) { + Log.e(TAG, e.toString()); + } + } + + /** + * Helper class holding a socket, the packet we are waiting the answer for, and a time + */ + private static class WaitingOnSocketPacket { + final DatagramSocket socket; + final IpPacket packet; + private final long time; + + WaitingOnSocketPacket(DatagramSocket socket, IpPacket packet) { + this.socket = socket; + this.packet = packet; + this.time = System.currentTimeMillis(); + } + + long ageSeconds() { + return (System.currentTimeMillis() - time) / 1000; + } + } + + /** + * Queue of WaitingOnSocketPacket, bound on time and space. + */ + private static class WospList implements Iterable { + private final LinkedList list = new LinkedList<>(); + + void add(WaitingOnSocketPacket wosp) { + if (list.size() > 1024) { + Log.d(TAG, "Dropping socket due to space constraints: " + list.element().socket); + list.element().socket.close(); + list.remove(); + } + while (!list.isEmpty() && list.element().ageSeconds() > 10) { + Log.d(TAG, "Timeout on socket " + list.element().socket); + list.element().socket.close(); + list.remove(); + } + list.add(wosp); + } + + public Iterator iterator() { + return list.iterator(); + } + + int size() { + return list.size(); + } + + } +} diff --git a/app/src/main/java/org/itxtech/daedalus/receiver/BootBroadcastReceiver.java b/app/src/main/java/org/itxtech/daedalus/receiver/BootBroadcastReceiver.java index f073998..deadd93 100644 --- a/app/src/main/java/org/itxtech/daedalus/receiver/BootBroadcastReceiver.java +++ b/app/src/main/java/org/itxtech/daedalus/receiver/BootBroadcastReceiver.java @@ -17,7 +17,8 @@ import org.itxtech.daedalus.util.DnsServerHelper; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class BootBroadcastReceiver extends BroadcastReceiver { @Override diff --git a/app/src/main/java/org/itxtech/daedalus/receiver/StatusBarBroadcastReceiver.java b/app/src/main/java/org/itxtech/daedalus/receiver/StatusBarBroadcastReceiver.java index 3ba26ed..a642e5a 100644 --- a/app/src/main/java/org/itxtech/daedalus/receiver/StatusBarBroadcastReceiver.java +++ b/app/src/main/java/org/itxtech/daedalus/receiver/StatusBarBroadcastReceiver.java @@ -17,7 +17,8 @@ import java.lang.reflect.Method; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class StatusBarBroadcastReceiver extends BroadcastReceiver { public static String STATUS_BAR_BTN_DEACTIVATE_CLICK_ACTION = "org.itxtech.daedalus.receiver.StatusBarBroadcastReceiver.STATUS_BAR_BTN_DEACTIVATE_CLICK_ACTION"; diff --git a/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java b/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java index 31e4b3d..14df70b 100644 --- a/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java +++ b/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java @@ -10,35 +10,19 @@ import android.net.VpnService; import android.os.Build; import android.os.ParcelFileDescriptor; import android.support.v7.app.NotificationCompat; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; -import android.system.StructPollfd; import android.util.Log; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Record; -import de.measite.minidns.record.A; import de.measite.minidns.util.InetAddressUtil; import org.itxtech.daedalus.Daedalus; import org.itxtech.daedalus.R; import org.itxtech.daedalus.activity.MainActivity; +import org.itxtech.daedalus.provider.DnsProvider; +import org.itxtech.daedalus.provider.TcpDnsProvider; +import org.itxtech.daedalus.provider.UdpDnsProvider; import org.itxtech.daedalus.receiver.StatusBarBroadcastReceiver; import org.itxtech.daedalus.util.DnsServerHelper; import org.itxtech.daedalus.util.HostsResolver; -import org.pcap4j.packet.*; -import org.pcap4j.packet.factory.PacketFactoryPropertiesLoader; -import org.pcap4j.util.PropertiesLoader; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.*; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Queue; +import java.net.Inet4Address; /** * Daedalus Project @@ -48,7 +32,8 @@ import java.util.Queue; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DaedalusVpnService extends VpnService implements Runnable { public static final String ACTION_ACTIVATE = "org.itxtech.daedalus.service.DaedalusVpnService.ACTION_ACTIVATE"; @@ -63,22 +48,12 @@ public class DaedalusVpnService extends VpnService implements Runnable { private static NotificationCompat.Builder notification = null; - private static long dnsQueryTimes = 0; - private static boolean localHostsResolve = false; - - private Thread mThread = null; - private ParcelFileDescriptor descriptor; private boolean running = false; private long lastUpdate = 0; + private boolean statisticQuery; + private DnsProvider provider; - private final WospList dnsIn = new WospList(); - private FileDescriptor mBlockFd = null; - private FileDescriptor mInterruptFd = null; - private final Queue deviceWrites = new LinkedList<>(); - /** - * Number of iterations since we last cleared the pcap4j cache - */ - private int pcap4jFactoryClearCacheCounter = 0; + private Thread mThread = null; @Override public void onCreate() { @@ -124,7 +99,6 @@ public class DaedalusVpnService extends VpnService implements Runnable { Daedalus.initHostsResolver(); DnsServerHelper.buildPortCache(); - dnsQueryTimes = 0; if (this.mThread == null) { this.mThread = new Thread(this, "DaedalusVpn"); this.running = true; @@ -149,21 +123,12 @@ public class DaedalusVpnService extends VpnService implements Runnable { private void stopThread() { boolean shouldRefresh = false; try { - if (this.descriptor != null) { - this.descriptor.close(); - this.descriptor = null; - } - if (this.mThread != null) { + if (mThread != null) { + running = false; shouldRefresh = true; - this.running = false; - this.mThread.interrupt(); - if (mInterruptFd != null) { - Os.close(mInterruptFd); - } - if (mBlockFd != null) { - Os.close(mBlockFd); - } - this.mThread = null; + provider.shutdown(); + mThread.interrupt(); + provider.stop(); } if (notification != null) { NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); @@ -181,7 +146,6 @@ public class DaedalusVpnService extends VpnService implements Runnable { Daedalus.updateShortcut(getApplicationContext()); } - dnsQueryTimes = 0; HostsResolver.clean(); DnsServerHelper.cleanPortCache(); } @@ -207,13 +171,9 @@ public class DaedalusVpnService extends VpnService implements Runnable { format = prefix; break; } - if (this.descriptor != null) { - this.descriptor.close(); - } boolean advanced = Daedalus.getPrefs().getBoolean("settings_advanced_switch", false); - boolean statisticQuery = Daedalus.getPrefs().getBoolean("settings_count_query_times", false); - localHostsResolve = Daedalus.getPrefs().getBoolean("settings_local_host_resolution", false); + statisticQuery = Daedalus.getPrefs().getBoolean("settings_count_query_times", false); Log.d(TAG, "tun0 add " + format + " pServ " + primaryServer + " sServ " + secondaryServer); Inet4Address primaryDNSServer = InetAddressUtil.ipv4From(primaryServer); Inet4Address secondaryDNSServer = InetAddressUtil.ipv4From(secondaryServer); @@ -230,96 +190,16 @@ public class DaedalusVpnService extends VpnService implements Runnable { .setBlocking(true); } - this.descriptor = builder.establish(); + ParcelFileDescriptor descriptor = builder.establish(); if (advanced) { - Log.d(TAG, "Starting advanced DNS proxy."); - 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 + dnsIn.size()]; - polls[0] = deviceFd; - polls[1] = blockFd; - { - int i = -1; - for (WaitingOnSocketPacket wosp : dnsIn) { - i++; - StructPollfd pollFd = polls[2 + i] = new StructPollfd(); - pollFd.fd = ParcelFileDescriptor.fromDatagramSocket(wosp.socket).getFileDescriptor(); - pollFd.events = (short) OsConstants.POLLIN; - } - } - - Log.d(TAG, "doOne: Polling " + polls.length + " file descriptors"); - Os.poll(polls, -1); - if (blockFd.revents != 0) { - Log.i(TAG, "Told to stop VPN"); - running = false; - return; - } - - // Need to do this before reading from the device, otherwise a new insertion there could - // invalidate one of the sockets we want to read from either due to size or time out - // constraints - { - int i = -1; - Iterator iter = dnsIn.iterator(); - while (iter.hasNext()) { - i++; - WaitingOnSocketPacket wosp = iter.next(); - if ((polls[i + 2].revents & OsConstants.POLLIN) != 0) { - Log.d(TAG, "Read from DNS socket" + wosp.socket); - iter.remove(); - handleRawDnsResponse(wosp.packet, wosp.socket); - wosp.socket.close(); - } - } - } - if ((deviceFd.revents & OsConstants.POLLOUT) != 0) { - Log.d(TAG, "Write to device"); - writeToDevice(outputStream); - } - if ((deviceFd.revents & OsConstants.POLLIN) != 0) { - Log.d(TAG, "Read from device"); - readPacketFromDevice(inputStream, packet); - } - - // pcap4j has some sort of properties cache in the packet factory. This cache leaks, so - // we need to clean it up. - if (++pcap4jFactoryClearCacheCounter % 1024 == 0) { - try { - PacketFactoryPropertiesLoader l = PacketFactoryPropertiesLoader.getInstance(); - Field field = l.getClass().getDeclaredField("loader"); - field.setAccessible(true); - PropertiesLoader loader = (PropertiesLoader) field.get(l); - Log.d(TAG, "Cleaning cache"); - loader.clearCache(); - } catch (NoSuchFieldException e) { - Log.e(TAG, "Cannot find declared loader field", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Cannot get declared loader field", e); - } - } - - if (statisticQuery) { - updateUserInterface(); - } + if (Daedalus.getPrefs().getBoolean("settings_dns_over_tcp", false)) { + provider = new TcpDnsProvider(descriptor, this); + } else { + provider = new UdpDnsProvider(descriptor, this); } + provider.start(); + provider.process(); } else { while (running) { Thread.sleep(1000); @@ -333,268 +213,34 @@ public class DaedalusVpnService extends VpnService implements Runnable { } } + public void providerLoopCallback() { + if (statisticQuery) { + updateUserInterface(); + } + } + private void updateUserInterface() { long time = System.currentTimeMillis(); if (time - lastUpdate >= 1000) { lastUpdate = time; Log.i(TAG, "notify"); - notification.setContentTitle(getResources().getString(R.string.notification_queries) + " " + String.valueOf(dnsQueryTimes)); + notification.setContentTitle(getResources().getString(R.string.notification_queries) + " " + String.valueOf(provider.getDnsQueryTimes())); NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(NOTIFICATION_ACTIVATED, notification.build()); } } - private void writeToDevice(FileOutputStream outFd) throws VpnNetworkException { - try { - outFd.write(deviceWrites.poll()); - } catch (IOException e) { - // TODO: Make this more specific, only for: "File descriptor closed" - throw new VpnNetworkException("Outgoing VPN output stream closed"); - } - } - private void readPacketFromDevice(FileInputStream inputStream, byte[] packet) throws VpnNetworkException, SocketException { - // Read the outgoing packet from the input stream. - int length; - - try { - length = inputStream.read(packet); - } catch (IOException e) { - throw new VpnNetworkException("Cannot read from device", e); - } - - - if (length == 0) { - // TODO: Possibly change to exception - Log.w(TAG, "Got empty packet!"); - return; - } - - final byte[] readPacket = Arrays.copyOfRange(packet, 0, length); - - handleDnsRequest(readPacket); - } - - private void forwardPacket(DatagramPacket outPacket, IpPacket parsedPacket) throws VpnNetworkException { - DatagramSocket dnsSocket; - try { - // Packets to be sent to the real DNS server will need to be protected from the VPN - dnsSocket = new DatagramSocket(); - - this.protect(dnsSocket); - - dnsSocket.send(outPacket); - - if (parsedPacket != null) { - dnsIn.add(new WaitingOnSocketPacket(dnsSocket, parsedPacket)); - } else { - dnsSocket.close(); - } - } catch (IOException e) { - if (e.getCause() instanceof ErrnoException) { - ErrnoException errnoExc = (ErrnoException) e.getCause(); - if ((errnoExc.errno == OsConstants.ENETUNREACH) || (errnoExc.errno == OsConstants.EPERM)) { - throw new VpnNetworkException("Cannot send message:", e); - } - } - Log.w(TAG, "handleDnsRequest: Could not send packet to upstream", e); - } - } - - private void queueDeviceWrite(IpPacket ipOutPacket) { - dnsQueryTimes++; - Log.i(TAG, "QT " + dnsQueryTimes); - deviceWrites.add(ipOutPacket.getRawData()); - } - - private void handleRawDnsResponse(IpPacket parsedPacket, DatagramSocket dnsSocket) throws IOException { - byte[] datagramData = new byte[1024]; - DatagramPacket replyPacket = new DatagramPacket(datagramData, datagramData.length); - dnsSocket.receive(replyPacket); - handleDnsResponse(parsedPacket, datagramData); - } - - - /** - * Handles a responsePayload from an upstream DNS server - * - * @param requestPacket The original request packet - * @param responsePayload The payload of the response - */ - private void handleDnsResponse(IpPacket requestPacket, byte[] responsePayload) { - 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 VpnNetworkException If some network error occurred - */ - private void handleDnsRequest(byte[] packetData) throws VpnNetworkException { - - IpPacket parsedPacket; - try { - parsedPacket = (IpPacket) IpSelector.newPacket(packetData, 0, packetData.length); - //TODO: get rid of pcap4j - } catch (Exception e) { - Log.i(TAG, "handleDnsRequest: Discarding invalid IP packet", e); - return; - } - - if (!(parsedPacket.getPayload() instanceof UdpPacket)) { - Log.i(TAG, "handleDnsRequest: Discarding unknown packet type " + parsedPacket.getPayload()); - return; - } - - InetAddress destAddr = parsedPacket.getHeader().getDstAddr(); - if (destAddr == null) - return; - - UdpPacket parsedUdp = (UdpPacket) parsedPacket.getPayload(); - - - if (parsedUdp.getPayload() == null) { - Log.i(TAG, "handleDnsRequest: Sending UDP packet without payload: " + parsedUdp); - - // Let's be nice to Firefox. Firefox uses an empty UDP packet to - // the gateway to reduce the RTT. For further details, please see - // https://bugzilla.mozilla.org/show_bug.cgi?id=888268 - DatagramPacket outPacket = new DatagramPacket(new byte[0], 0, 0, destAddr, - DnsServerHelper.getPortOrDefault(destAddr, parsedUdp.getHeader().getDstPort().valueAsInt())); - forwardPacket(outPacket, null); - return; - } - - byte[] dnsRawData = (parsedUdp).getPayload().getRawData(); - DNSMessage dnsMsg; - try { - dnsMsg = new DNSMessage(dnsRawData); - } catch (IOException e) { - Log.i(TAG, "handleDnsRequest: Discarding non-DNS or invalid packet", e); - return; - } - if (dnsMsg.getQuestion() == null) { - Log.i(TAG, "handleDnsRequest: Discarding DNS packet with no query " + dnsMsg); - return; - } - String dnsQueryName = dnsMsg.getQuestion().name.toString(); - - try { - if (localHostsResolve && HostsResolver.canResolve(dnsQueryName)) { - String response = HostsResolver.resolve(dnsQueryName); - Log.i(TAG, "handleDnsRequest: DNS Name " + dnsQueryName + " address " + response + ", using local hosts to resolve."); - DNSMessage.Builder builder = dnsMsg.asBuilder(); - int[] ip = new int[4]; - byte i = 0; - for (String block : response.split("\\.")) { - ip[i] = Integer.parseInt(block); - i++; - } - builder.addAnswer(new Record<>(dnsQueryName, Record.TYPE.getType(A.class), 1, 64, new A(ip[0], ip[1], ip[2], ip[3]))); - handleDnsResponse(parsedPacket, builder.build().toArray()); - } else { - Log.i(TAG, "handleDnsRequest: DNS Name " + dnsQueryName + " , 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) { - Log.e(TAG, e.toString()); - } - } - - static class VpnNetworkException extends Exception { - VpnNetworkException(String s) { + public static class VpnNetworkException extends Exception { + public VpnNetworkException(String s) { super(s); } - VpnNetworkException(String s, Throwable t) { + public VpnNetworkException(String s, Throwable t) { super(s, t); } } - /** - * Helper class holding a socket, the packet we are waiting the answer for, and a time - */ - private static class WaitingOnSocketPacket { - final DatagramSocket socket; - final IpPacket packet; - private final long time; - - WaitingOnSocketPacket(DatagramSocket socket, IpPacket packet) { - this.socket = socket; - this.packet = packet; - this.time = System.currentTimeMillis(); - } - - long ageSeconds() { - return (System.currentTimeMillis() - time) / 1000; - } - } - - /** - * Queue of WaitingOnSocketPacket, bound on time and space. - */ - private static class WospList implements Iterable { - private final LinkedList list = new LinkedList<>(); - - void add(WaitingOnSocketPacket wosp) { - if (list.size() > 1024) { - Log.d(TAG, "Dropping socket due to space constraints: " + list.element().socket); - list.element().socket.close(); - list.remove(); - } - while (!list.isEmpty() && list.element().ageSeconds() > 10) { - Log.d(TAG, "Timeout on socket " + list.element().socket); - list.element().socket.close(); - list.remove(); - } - list.add(wosp); - } - - public Iterator iterator() { - return list.iterator(); - } - - int size() { - return list.size(); - } - - } } diff --git a/app/src/main/java/org/itxtech/daedalus/util/Configurations.java b/app/src/main/java/org/itxtech/daedalus/util/Configurations.java index 3460092..c2de7a7 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/Configurations.java +++ b/app/src/main/java/org/itxtech/daedalus/util/Configurations.java @@ -18,7 +18,8 @@ import java.util.ArrayList; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class Configurations { private static final int CUSTOM_DNS_ID_START = 32; diff --git a/app/src/main/java/org/itxtech/daedalus/util/CustomDnsServer.java b/app/src/main/java/org/itxtech/daedalus/util/CustomDnsServer.java index 2be822c..f171826 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/CustomDnsServer.java +++ b/app/src/main/java/org/itxtech/daedalus/util/CustomDnsServer.java @@ -10,7 +10,8 @@ import org.itxtech.daedalus.Daedalus; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class CustomDnsServer { private String name; diff --git a/app/src/main/java/org/itxtech/daedalus/util/DnsServer.java b/app/src/main/java/org/itxtech/daedalus/util/DnsServer.java index 98d1525..6bc375b 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/DnsServer.java +++ b/app/src/main/java/org/itxtech/daedalus/util/DnsServer.java @@ -10,7 +10,8 @@ import android.content.Context; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsServer { public static int DNS_SERVER_DEFAULT_PORT = 53; diff --git a/app/src/main/java/org/itxtech/daedalus/util/DnsServerHelper.java b/app/src/main/java/org/itxtech/daedalus/util/DnsServerHelper.java index c805ac4..ec15a88 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/DnsServerHelper.java +++ b/app/src/main/java/org/itxtech/daedalus/util/DnsServerHelper.java @@ -15,7 +15,8 @@ import java.util.HashMap; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class DnsServerHelper { private static HashMap portCache = null; diff --git a/app/src/main/java/org/itxtech/daedalus/util/HostsProvider.java b/app/src/main/java/org/itxtech/daedalus/util/HostsProvider.java index 4a56495..c20085f 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/HostsProvider.java +++ b/app/src/main/java/org/itxtech/daedalus/util/HostsProvider.java @@ -12,7 +12,8 @@ import java.util.ArrayList; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class HostsProvider { private String name; diff --git a/app/src/main/java/org/itxtech/daedalus/util/HostsResolver.java b/app/src/main/java/org/itxtech/daedalus/util/HostsResolver.java index c80e016..60992de 100644 --- a/app/src/main/java/org/itxtech/daedalus/util/HostsResolver.java +++ b/app/src/main/java/org/itxtech/daedalus/util/HostsResolver.java @@ -16,7 +16,8 @@ import java.util.HashMap; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class HostsResolver implements Runnable { private static final String TAG = "DHostsResolver"; diff --git a/app/src/main/java/org/itxtech/daedalus/view/ClearAutoCompleteTextView.java b/app/src/main/java/org/itxtech/daedalus/view/ClearAutoCompleteTextView.java index aee739c..260ae1c 100644 --- a/app/src/main/java/org/itxtech/daedalus/view/ClearAutoCompleteTextView.java +++ b/app/src/main/java/org/itxtech/daedalus/view/ClearAutoCompleteTextView.java @@ -20,7 +20,8 @@ import org.itxtech.daedalus.R; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class ClearAutoCompleteTextView extends AutoCompleteTextView implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher { diff --git a/app/src/main/java/org/itxtech/daedalus/view/ClickPreference.java b/app/src/main/java/org/itxtech/daedalus/view/ClickPreference.java index e8c7b67..b0fcce5 100644 --- a/app/src/main/java/org/itxtech/daedalus/view/ClickPreference.java +++ b/app/src/main/java/org/itxtech/daedalus/view/ClickPreference.java @@ -1,6 +1,8 @@ package org.itxtech.daedalus.view; +import android.annotation.TargetApi; import android.content.Context; +import android.os.Build; import android.preference.ListPreference; import android.util.AttributeSet; @@ -12,10 +14,12 @@ import android.util.AttributeSet; *

* 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, version 3. + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. */ public class ClickPreference extends ListPreference { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ClickPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c1bcbf4..6238146 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -63,4 +63,5 @@ 删除 应用 请填写所有配置项。 + DNS over TCP \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00f4da9..0d5f912 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,4 +63,5 @@ Delete Apply Please fill in all configuration items. + DNS over TCP diff --git a/app/src/main/res/xml/perf_settings.xml b/app/src/main/res/xml/perf_settings.xml index 0da1ae3..cfa7c1e 100644 --- a/app/src/main/res/xml/perf_settings.xml +++ b/app/src/main/res/xml/perf_settings.xml @@ -59,6 +59,11 @@ android:title="@string/settings_hosts_pan_domain_resolution" android:defaultValue="false" android:enabled="false"/> +