/*******************************************************************************
* 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.encDNSloadGen_v0_001;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.ArrayUtils;
import staticContent.framework.EncDnsClient;
import staticContent.framework.controller.Implementation;
import staticContent.framework.interfaces.Layer1NetworkClient;
import staticContent.framework.interfaces.Layer2RecodingSchemeClient;
import staticContent.framework.interfaces.Layer5ApplicationClient;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer2recodingScheme.encDNS_v0_001.EncDNSHelper;
import userGeneratedContent.testbedPlugIns.layerPlugIns.layer5application.encDNS_v0_001.EncDnsRequest;
/**
* local proxy for encdns (EncDNS-Proxy); stub resolver is supposed to
* communicate with this component
*
* ---
*
* This is the EncDNS client proxy (also known as local proxy). It should be
* executed on the client computer or at least be connected to it via a
* trustworthy connection. The client proxy will encrypt standard DNS requests
* sent to it by a stub resolver and pass the encrypted request on to the local
* recursive nameserver. It will also decrypt the encrypted response received
* from the local recursive nameserver and pass the decrypted standard DNS
* response on to the stub resolver.
*
* This implementation currently supports UDP only.
*/
public class ClientPlugIn extends Implementation implements Layer5ApplicationClient {
/** maximum DNS message size */
public static final int MAX_MSG_SIZE = 65535;
private EncDnsClient owner;
private Layer2RecodingSchemeClient layer2;
private Layer1NetworkClient layer1;
private SecureRandom _rnd;
private long SEND_INTERVAL_IN_NS;
private int THREADS;
private boolean AFAP = false;
private final long SLEEP_PRECISION = TimeUnit.MILLISECONDS.toNanos(2); // TODO: determine for current machine
private final long INIT_TIME = System.nanoTime();
private byte[] _qname;
private short _qtype;
private short _qclass;
private int _qNum;
@Override
public void constructor() {
System.out.println("EncDNS load generator started");
this.owner = EncDnsClient.getInstance();
if (settings.getPropertyAsInt("EDNS_LG_MSG_PER_SEC") == -1)
AFAP = true;
else
this.SEND_INTERVAL_IN_NS = (int)Math.ceil(1000000000d/settings.getPropertyAsDouble("EDNS_LG_MSG_PER_SEC"));
this.THREADS = settings.getPropertyAsInt("EDNS_LG_THREADS");
this._qclass = settings.getPropertyAsShort("EDNS_LG_Q_CLASS");
this._qname = EncDNSHelper.parseZoneNameString(settings.getProperty("EDNS_LG_Q_NAME"));
this._qtype = (short)settings.getPropertyAsInt("EDNS_LG_Q_TYPE"); // This must be parsed as an Integer as otherwise values having a highest-value bit of 1 would be considered out of range
if(this._qtype>65535)
throw new RuntimeException("EDNS_LG_Q_TYPE must be <= 65535!");
this._qNum = 0;
this._rnd = new SecureRandom();
StatisticsRecorder.init(settings.getPropertyAsInt("EDNS_LG_DISPLAY_STAT_PERIOD"), THREADS);
}
@Override
public void initialize() {
this.layer2 = (Layer2RecodingSchemeClient) owner.recodingLayerClient.getImplementation();
this.layer1 = (Layer1NetworkClient) owner.networkLayerClient.getImplementation();
}
@Override
public void begin() {
for (int i=0; i<THREADS; i++)
new SenderThread().start();
// TODO: start display-stat-thread
}
public class SenderThread extends Thread {
long nextOutput;
final int threadId = StatisticsRecorder.getThreadId();
final long sendInterval = Math.round(((double)SEND_INTERVAL_IN_NS)*((double)THREADS));
@Override
public void run() {
if (AFAP) {
while (true) {
EncDnsRequest request = new EncDnsRequest(null, 0, getQueryBytes());
request = (EncDnsRequest) layer2.applyLayeredEncryption(request); // skip layer 3 + 4...
if (request == null || request.getByteMessage() == null)
continue;
layer1.sendMessage(request);
StatisticsRecorder.messageSent(threadId);
}
} else {
nextOutput = now() + sendInterval;
while (true) {
long now = now();
if (now < nextOutput) // we must wait
sleepNanos(nextOutput - now);
nextOutput += sendInterval;
EncDnsRequest request = new EncDnsRequest(null, 0, getQueryBytes());
request = (EncDnsRequest) layer2.applyLayeredEncryption(request); // skip layer 3 + 4...
if (request == null || request.getByteMessage() == null)
continue;
layer1.sendMessage(request);
StatisticsRecorder.messageSent(threadId);
}
}
}
}
/**
* Creates a new query message with a unique prefix.
* @return query message
*/
public byte[] getQueryBytes() {
byte[] label1length = {36};
byte[] label1 = ("a123456789a123456789a123456789a12345".getBytes());
byte[] prefix = new byte[19];
prefix[0] = 18;
prefix[1] = 'a';
prefix[2] = '1';
prefix[3] = '2';
prefix[4] = '3';
prefix[5] = '4';
prefix[6] = '5';
prefix[7] = '6';
prefix[8] = '7';
prefix[9] = '8';
prefix[10] = '9';
prefix[11] = 'w';
String numstr = String.format("%08d", _qNum);
if(_qNum>=99999999) {
_qNum = 0;
} else {
_qNum++;
}
for(int i = 12; i < prefix.length; i++) {
prefix[i] = (byte)numstr.charAt(i-12);
}
byte[] qname = ArrayUtils.addAll(prefix, label1length);
qname = ArrayUtils.addAll(qname, label1);
//qname = ArrayUtils.addAll(qname, label1length);
//qname = ArrayUtils.addAll(qname, label1);
qname = ArrayUtils.addAll(qname, _qname);
//byte[] qname = ArrayUtils.addAll(prefix, _qname);
byte[] query = buildQuery(qname, _qtype, _qclass);
return query;
}
/**
* Builds the query message
* @param qName query name
* @param type query type
* @param qClass query class
* @return query message
*/
private byte[] buildQuery(byte[] qName, short type, short qClass) {
ByteBuffer buf = ByteBuffer.allocate(MAX_MSG_SIZE);
// --- DNS HEADER ---
// Choose random ID
byte[] id = new byte[2];
_rnd.nextBytes(id);
buf.put(id);
buf.put((byte) 1); // QR=0 (Query), OpCode=0000 (Query), AA=0, TC=0, RD=1
buf.put((byte) 0); // RA,Z,AD,CD,RCode=0
buf.putShort((short) 1); // question count = 1
buf.putShort((short) 0); // answer count = 0
buf.putShort((short) 0); // authority count = 0
buf.putShort((short) 0); // additional count = 0
// --- DNS QUESTION SECTION ---
buf.put(qName); // NAME
buf.putShort(type); // TYPE
buf.putShort(qClass); // CLASS
// convert to byte[]
byte[] out = new byte[buf.position()];
buf.position(0);
buf.get(out);
return out;
}
private long now() {
return System.nanoTime() - INIT_TIME;
}
private void sleepNanos(long nanoDuration) {
// see http://andy-malakov.blogspot.de/2010/06/alternative-to-threadsleep.html
final long end = now() + nanoDuration;
long timeLeft = nanoDuration;
try {
do {
if (timeLeft > SLEEP_PRECISION)
Thread.sleep(1);
else
Thread.yield();
timeLeft = end - now();
if (Thread.interrupted())
throw new InterruptedException();
} while (timeLeft > 0/* && !interruptSleep*/);
} catch (InterruptedException e) {
if (timeLeft > 0/* && !interruptSleep*/)
sleepNanos(end - now());
}
}
}