/**
* Basic DNS Client
*
* @author pquiring
*
* Created : Nov 17, 2013
*/
import java.io.*;
import java.nio.*;
import java.net.*;
import java.util.*;
public class DNS extends Thread {
private NetApp win;
private DatagramSocket ds;
private static int maxmtu = 512; //standard
private String server, query;
private int type;
private short id;
public DNS(NetApp win, String server, String query, String type) {
this.win = win;
this.server = server;
this.query = query;
this.type = decodeType(type);
}
public void run() {
if (false) {
runJava();
} else {
runDNS();
}
}
private void runJava() {
try {
win.setDNSStatus("Resolving host...");
InetAddress ia = InetAddress.getByName(query);
win.setDNSIP(ia.getHostAddress().toString());
} catch (Exception e) {
e.printStackTrace();
win.setDNSStatus(e.toString());
}
}
private void runDNS() {
try {
ds = new DatagramSocket(); //bind to next available port
id = (short)(new Random().nextInt() & 0x7fff);
//build request
buildRequest(query, type, id);
//send request
DatagramPacket out = new DatagramPacket(data, dataOffset);
out.setAddress(InetAddress.getByName(server));
out.setPort(53);
ds.send(out);
//wait for reply
data = new byte[maxmtu];
DatagramPacket in = new DatagramPacket(data, data.length);
ds.setSoTimeout(2500);
ds.receive(in);
ds.close();
ds = null;
//process reply
processReply(in.getData(), in.getLength());
} catch (Exception e) {
e.printStackTrace();
win.setDNSStatus(e.toString());
}
if (ds != null) {
ds.close();
ds = null;
}
}
public void close() {
if (ds != null) {
ds.close();
ds = null;
}
}
//flags
private static final int QR = 0x8000; //1=response (0=query)
//4 bits opcode
private static final int OPCODE_QUERY = 0x0000;
private static final int OPCODE_IQUERY = 0x4000;
private static final int AA = 0x0400; //auth answer
private static final int TC = 0x0200; //truncated (if packet > 512 bytes)
private static final int RD = 0x0100; //recursive desired
private static final int RA = 0x0080; //recursive available
private static final int Z = 0x0040; //???
private static final int AD = 0x0020; //auth data???
private static final int CD = 0x0010; //checking disabled???
//4 bits result code (0=no error)
private static final int A = 1;
private static final int CNAME = 5;
private static final int MX = 15;
private static final int AAAA = 28;
private byte data[];
private int dataOffset;
private ByteBuffer buffer;
private int nameLength; //bytes used decoding name
private String getName(byte data[], int offset) {
StringBuilder name = new StringBuilder();
boolean jump = false;
nameLength = 0;
do {
if (!jump) nameLength++;
int length = data[offset++] & 0xff;
if (length == 0) break;
if (length >= 0xc0) {
//pointer
if (!jump) nameLength++;
jump = true;
int newOffset = (length & 0x3f) << 8;
newOffset += data[offset] & 0xff;
offset = newOffset;
} else {
if (!jump) nameLength += length;
if (name.length() != 0) name.append(".");
name.append(new String(data, offset, length));
offset += length;
}
} while (true);
return name.toString();
}
private int encodeName(String domain) {
//TODO : compression
String p[] = domain.split("[.]");
int length = 0;
int strlen;
for(int a=0;a<p.length;a++) {
strlen = p[a].length();
data[dataOffset++] = (byte)strlen;
length++;
System.arraycopy(p[a].getBytes(), 0, data, dataOffset, strlen);
dataOffset += strlen;
length += strlen;
}
//zero length part ends string
data[dataOffset++] = 0;
length++;
return length;
}
private void buildRequest(String query, int type, int id) {
data = new byte[maxmtu];
buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.BIG_ENDIAN);
dataOffset = 0;
putShort((short)id);
putShort((short)(RD)); //flags
putShort((short)1); //questions
putShort((short)0); //answers
putShort((short)0); //name servers
putShort((short)0); //additionals
encodeName(query);
putShort((short)type);
putShort((short)1); //class
switch(type) {
case A:
encodeName(query);
putShort((short)type);
putShort((short)1); //class
break;
case CNAME:
encodeName(query);
putShort((short)type);
putShort((short)1); //class
break;
case MX:
encodeName(query);
putShort((short)type);
putShort((short)1); //class
break;
case AAAA:
encodeName(query);
putShort((short)type);
putShort((short)1); //class
break;
}
}
private int decodeType(String type) {
if (type.equals("A")) return A;
if (type.equals("AAAA")) return AAAA;
if (type.equals("MX")) return MX;
return -1;
}
private String typeToString(int type) {
switch (type) {
case MX: return "MX";
case A: return "A";
case AAAA: return "AAAA";
case CNAME: return "CNAME";
default: return "?" + type;
}
}
private String decodeData(byte data[], int offset, int type) {
StringBuilder sb = new StringBuilder();
switch (type) {
case A:
sb.append("IP4=");
for(int a=0;a<4;a++) {
if (a > 0) sb.append('.');
sb.append(Integer.toString(data[offset+a] & 0xff));
}
break;
case CNAME:
sb.append("CNAME=");
sb.append(getName(data, offset));
break;
case MX:
sb.append("MX=");
int pref = data[offset++] & 0xff;
pref <<= 8;
pref += data[offset++] & 0xff;
sb.append(getName(data, offset));
sb.append(":pref=" + pref);
break;
case AAAA:
sb.append("IP6=");
for(int a=0;a<8;a++) {
if (a > 0) sb.append(':');
int o = data[offset+a*2] & 0xff;
o <<= 8;
o += data[offset+a*2+1] & 0xff;
sb.append(String.format("%04x", o));
}
break;
default:
sb.append("unsupported type:" + type);
break;
}
return sb.toString();
}
private void processReply(byte data[], int dataLength) throws Exception {
ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.BIG_ENDIAN);
short id = bb.getShort(0);
if (id != this.id) throw new Exception("id does not match:" + Integer.toString(id, 16) + "!=" + Integer.toString(this.id, 16));
short flgs = bb.getShort(2);
if ((flgs & QR) == 0) {
throw new Exception("not a response");
}
short cntQ = bb.getShort(4);
if (cntQ != 1) throw new Exception("only 1 question supported");
short cntA = bb.getShort(6);
// if (cndA != 0) throw new Exception("query with answers?");
short cntS = bb.getShort(8);
// if (cndS != 0) throw new Exception("query with auth names?");
short cntAdd = bb.getShort(10);
// if (cndAdd != 0) throw new Exception("query with adds?");
int offset = 12;
StringBuilder sb = new StringBuilder();
//questions
for(int a=0;a < cntQ; a++) {
String query = getName(data, offset);
offset += nameLength;
int type = bb.getShort(offset);
offset += 2;
int cls = bb.getShort(offset);
if (cls != 1) throw new Exception("only internet class supported:" + cls);
offset += 2;
sb.append("Query:" + query + ":" + typeToString(type) + "\n");
}
//anwsers
for(int a=0;a < cntA; a++) {
String query = getName(data, offset);
offset += nameLength;
int type = bb.getShort(offset);
offset += 2;
int cls = bb.getShort(offset);
if (cls != 1) throw new Exception("only internet class supported:" + cls);
offset += 2;
int ttl = bb.getInt(offset);
offset += 4;
short rdataLength = bb.getShort(offset);
offset += 2;
sb.append("Anwser:" + query + ":" + typeToString(type) + ":TTL=" + ttl + ":" + decodeData(data, offset, type) + "\n");
offset += rdataLength;
}
//auth server
for(int a=0;a < cntS; a++) {
String query = getName(data, offset);
offset += nameLength;
int type = bb.getShort(offset);
offset += 2;
int cls = bb.getShort(offset);
if (cls != 1) throw new Exception("only internet class supported:" + cls);
offset += 2;
int ttl = bb.getInt(offset);
offset += 4;
short rdataLength = bb.getShort(offset);
offset += 2;
sb.append("AuthServer:" + query + ":" + typeToString(type) + ":TTL=" + ttl + ":" + decodeData(data, offset, type) + "\n");
offset += rdataLength;
}
//additional
for(int a=0;a < cntAdd; a++) {
String query = getName(data, offset);
offset += nameLength;
int type = bb.getShort(offset);
offset += 2;
int cls = bb.getShort(offset);
if (cls != 1) throw new Exception("only internet class supported:" + cls);
offset += 2;
int ttl = bb.getInt(offset);
offset += 4;
short rdataLength = bb.getShort(offset);
offset += 2;
sb.append("Additional:" + query + ":" + typeToString(type) + ":TTL=" + ttl + ":" + decodeData(data, offset, type) + "\n");
offset += rdataLength;
}
win.setDNSIP(sb.toString());
}
private void putIP4(String ip) {
String p[] = ip.split("[.]");
for(int a=0;a<4;a++) {
data[dataOffset++] = (byte)(int)Integer.valueOf(p[a]);
}
}
private void putIP6(String ip) {
String p[] = ip.split(":");
for(int a=0;a<8;a++) {
putShort((short)(int)Integer.valueOf(p[a], 16));
}
}
private void putShort(short value) {
buffer.putShort(dataOffset, value);
dataOffset += 2;
}
private void putInt(int value) {
buffer.putInt(dataOffset, value);
dataOffset += 4;
}
}
/*
struct Packet {
short id;
short flgs;
short queries_cnt;
short anwsers_cnt;
short auth_servers_cnt;
short additional_cnt;
Queries[];
AnwserResources[];
AuthServerResources[];
AdditionalResources[];
};
struct Query {
DNSName query;
short type;
short cls;
};
struct Resource {
DNSName name;
short type;
short cls;
int ttl;
short rdataLength;
byte rdata[rdataLength];
};
DNS Names compression:
[3]www[6]google[3]com[0]
Any length that starts with 11 binary is a 2 byte (14bits) pointer from the first byte of the packet.
[4]mail[pointer:33] -> assuming 33 points to [6]google[3]com[0] this would => mail.google.com
Types: 1=IP4 5=CNAME 15=MX 28=IP6 etc.
Cls : 1=Internet
*/