/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.dnsProxy_v0_001;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.xbill.DNS.Cache;
import org.xbill.DNS.Credibility;
import org.xbill.DNS.DClass;
import org.xbill.DNS.ExtendedResolver;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import org.xbill.DNS.SetResponse;
import staticContent.framework.controller.Implementation;
import staticContent.framework.interfaces.Layer5ApplicationMix;
import staticContent.framework.routing.RoutingMode;
import staticContent.framework.socket.socketInterfaces.AnonMessage;
import staticContent.framework.socket.socketInterfaces.DatagramAnonServerSocket;
import staticContent.framework.socket.socketInterfaces.AnonSocketOptions.CommunicationDirection;
import staticContent.framework.socket.socketInterfaces.NoneBlockingAnonSocketOptions.IO_Mode;
import staticContent.framework.util.Util;
public class MixPlugIn extends Implementation implements Layer5ApplicationMix {
private DatagramAnonServerSocket socket = null;
/** Flag indicating if all DNS queries shall get an A record of 127.0.0.1 instead of
* really resolving them through the DNS server */
private boolean RESOLVE_ALL_DNS_TO_LOCALHOST;
/** The resolver object to use for resolving the DNS queries */
private static ExtendedResolver res;
/** Random number generator for performing the DNS lookups with a random DNS server */
private static final Random r = new SecureRandom();
/** Terminating sequence used in batched DNS messages to signal end of a message part */
//private static final byte[] dnsTerminator = new byte[]{(byte)0xfa,(byte)0xfb,(byte)0xfc,(byte)0xfd,(byte)0xfe};
/** Local DNS cache that answers queries if the records are cached */
private Cache dnsCache = new Cache(DClass.IN);
private int RESOLVER_TIMEOUT;
/** The DNS servers used for resolving incoming queries */
private static final String[] dnsServers = new String[]{// TODO add this to the property file
"8.8.4.4"//,
/*"193.101.111.10",
"131.188.3.72",
"208.67.222.222",
"85.214.73.63",
"194.150.168.168",
"208.67.222.222",
"156.154.70.1"*/
};
/*private static final String[] dnsServers = new String[]{
"8.8.8.8", // Google
"8.8.4.4", // Google
"213.73.91.35", // dnscache.berlin.ccc.de
"85.214.73.63", // anonymisierungsdienst.foebud.org
//"204.152.184.76", // f.6to4-servers.net - Slow
"194.150.168.168", // dns.as250.net
"80.237.196.2", // dnsc1.dtfh.de
"194.95.202.198", // omni.digital.udk-berlin.de
"67.138.54.100", // ScrubIT
"207.225.209.66", //ScrubIT
"208.67.222.222", //OpenDNS
"208.67.220.220", // OpenDNS
"156.154.70.1", // DNS Advantage
"156.154.71.1", // DNS Advantage
"4.2.2.1", // vnsc-pri.sys.gtei.net
"4.2.2.2", // vnsc-bak.sys.gtei.net
"4.2.2.3", // vnsc-lc.sys.gtei.net
"4.2.2.4", // vnsc-pri-dsl.genuity.net
"4.2.2.5", // vnsc-bak-dsl.genuity.net
"4.2.2.6" // vnsc-lc-dsl.genuity.net
};*/
private Executor exec;
private boolean DNSP_DEBUG;
@Override
public void constructor() {
this.DNSP_DEBUG = settings.getPropertyAsBoolean("DNSP_DEBUG");
this.RESOLVE_ALL_DNS_TO_LOCALHOST = settings.getPropertyAsBoolean("RESOLVE_ALL_DNS_TO_LOCALHOST");
this.RESOLVER_TIMEOUT = settings.getPropertyAsInt("RESOLVER_TIMEOUT");
this.exec = Executors.newFixedThreadPool(settings.getPropertyAsInt("RESOLVER_THREADS"));
}
@Override
public void initialize() {
this.socket = super.anonNode.createDatagramServerSocket(
settings.getPropertyAsInt("INTERNAL_MIX_PORT"),
CommunicationDirection.DUPLEX,
IO_Mode.BLOCKING,
false,
false,
super.anonNode.ROUTING_MODE != RoutingMode.GLOBAL_ROUTING
);
}
@Override
public void begin() {
setUpResolver();
new RequestReceiverThread().start(); // TODO: use observer-pattern
}
/**
* The {@code ExtendedResolver} is instantiated with the
* DNS servers from the <code>dnsServers</code> array.
* Load balancing will be performed when resolving.
* (Probably does not apply to asynchronous resolving)
*
*/
private void setUpResolver() {
try {
res = new ExtendedResolver(dnsServers);
res.setLoadBalance(true);
res.setTimeout(RESOLVER_TIMEOUT);
res.setRetries(0);
} catch (UnknownHostException e) {
e.printStackTrace();
throw new RuntimeException("could not set up resolver");
}
}
private class RequestReceiverThread extends Thread {
/**
* Waits for processed <code>Requests</code> and puts their payload in the
* suiting <code>User</code>'s proxy buffer.
*
* @see de.ur.sec.dnsmix.userDatabase.User#putInProxyWriteBuffer(byte[])
*/
@Override
public void run() {
while (true) {
AnonMessage datagram = socket.receiveMessage();
if(DNSP_DEBUG)
System.out.println("mix received mix message (" +Util.toHex(datagram.getByteMessage()) +")");
// the raw byte[] message could contain multiple DNS queries
Payload payload = new Payload(datagram.getMaxReplySize());
payload.setMessage(datagram.getByteMessage());
// now we have a list that can either be size 1 or > 1 if the raw byte message contained more messages
List<byte[]> dnsQueriesWithID = payload.getMessages(0);
for (byte[] dnsQueryWithID : dnsQueriesWithID) {
exec.execute(new DNSResolver(datagram, dnsQueryWithID));
}
}
}
}
class DNSResolver implements Runnable {
private byte[] msgID;
private byte[] queryInWire;
//private User channel;
private AnonMessage datagram;
DNSResolver(AnonMessage datagram, byte[] dnsQueryWithMsgID){
//this.channel = datagram.getUser();
this.msgID = Arrays.copyOfRange(dnsQueryWithMsgID, 0, 4);
this.queryInWire = Arrays.copyOfRange(dnsQueryWithMsgID, 4, dnsQueryWithMsgID.length);
if(DNSP_DEBUG)
System.out.println(" mix unpacked query: " + Util.byteArrayToInt(msgID) +" (" +Util.toHex(queryInWire) +")");
this.datagram = datagram;
}
@Override
public void run() {
Message dnsQuery;
Message dnsReply;
// TODO check DNS cache
try {
dnsQuery = new Message(queryInWire);
byte[] data;
SetResponse cached = dnsCache.lookupRecords(dnsQuery.getQuestion().getName(), dnsQuery.getQuestion().getType(), Credibility.NONAUTH_ANSWER);
if (cached.isSuccessful() && !RESOLVE_ALL_DNS_TO_LOCALHOST) {
Record[] r = DNSUtils.processSetResponse(cached);
for (Record rec : r){
dnsQuery.addRecord(rec, Section.ANSWER);
}
dnsQuery.getHeader().setFlag(Flags.RA);
dnsQuery.getHeader().setFlag(Flags.QR);
data = dnsQuery.toWire();
} else if(RESOLVE_ALL_DNS_TO_LOCALHOST){
data = DNSUtils.mergeArrays(msgID, DNSUtils.resolveDNSQueryToLocalhost(dnsQuery.toWire()));
} else {
dnsReply = res.getResolver(r.nextInt(dnsServers.length)).send(dnsQuery);
data = DNSUtils.mergeArrays(msgID, dnsReply.toWire());
}
if ((data.length + Payload.getHeaderLength()) > datagram.getMaxReplySize()) {
System.err.println("warning: reply too big for mix message; discarting it");
return;
}
Payload replyPayload = new Payload(
new byte[data.length + Payload.getHeaderLength()],
datagram.getMaxReplySize()
);
replyPayload.setMessage(data);
datagram.setByteMessage(replyPayload.getBytePayloadWithLengthHeader());
if(DNSP_DEBUG)
System.out.println(" mix sending reply-mix-message (" +Util.toHex(replyPayload.getBytePayloadWithLengthHeader()) +")");
socket.sendMessage(datagram);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}