525 lines
14 KiB
Java
525 lines
14 KiB
Java
package de.measite.minidns;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.DataInputStream;
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
|
|
/**
|
|
* A DNS message as defined by rfc1035. The message consists of a header and
|
|
* 4 sections: question, answer, nameserver and addition resource record
|
|
* section.
|
|
* A message can either be parsed ({@link DNSMessage#parse(byte[])}) or serialized
|
|
* ({@link DNSMessage#toArray()}).
|
|
*/
|
|
public class DNSMessage {
|
|
|
|
/**
|
|
* Possible DNS reply codes.
|
|
*/
|
|
public static enum RESPONSE_CODE {
|
|
NO_ERROR(0), FORMAT_ERR(1), SERVER_FAIL(2), NX_DOMAIN(3),
|
|
NO_IMP(4), REFUSED(5), YXDOMAIN(6), YXRRSET(7),
|
|
NXRRSET(8), NOT_AUTH(9),NOT_ZONE(10);
|
|
|
|
/**
|
|
* Reverse lookup table for response codes.
|
|
*/
|
|
private final static RESPONSE_CODE INVERSE_LUT[] = new RESPONSE_CODE[]{
|
|
NO_ERROR, FORMAT_ERR, SERVER_FAIL, NX_DOMAIN, NO_IMP,
|
|
REFUSED, YXDOMAIN, YXRRSET, NXRRSET, NOT_AUTH, NOT_ZONE,
|
|
null, null, null, null, null
|
|
};
|
|
|
|
/**
|
|
* The response code value.
|
|
*/
|
|
private final byte value;
|
|
|
|
/**
|
|
* Create a new response code.
|
|
* @param value The response code value.
|
|
*/
|
|
private RESPONSE_CODE(int value) {
|
|
this.value = (byte)value;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the byte value of the response code.
|
|
* @return the response code.
|
|
*/
|
|
public byte getValue() {
|
|
return (byte) value;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the response code for a byte value.
|
|
* @param value The byte value.
|
|
* @return The symbolic response code or null.
|
|
* @throws IllegalArgumentException if the value is not in the range of
|
|
* 0..15.
|
|
*/
|
|
public static RESPONSE_CODE getResponseCode(int value) {
|
|
if (value < 0 || value > 15) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
return INVERSE_LUT[value];
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Symbolic DNS Opcode values.
|
|
*/
|
|
public static enum OPCODE {
|
|
QUERY(0),
|
|
INVERSE_QUERY(1),
|
|
STATUS(2),
|
|
NOTIFY(4),
|
|
UPDATE(5);
|
|
|
|
/**
|
|
* Lookup table for for obcode reolution.
|
|
*/
|
|
private final static OPCODE INVERSE_LUT[] = new OPCODE[]{
|
|
QUERY, INVERSE_QUERY, STATUS, null, NOTIFY, UPDATE, null,
|
|
null, null, null, null, null, null, null, null
|
|
};
|
|
|
|
/**
|
|
* The value of this opcode.
|
|
*/
|
|
private final byte value;
|
|
|
|
/**
|
|
* Create a new opcode for a given byte value.
|
|
* @param value The byte value of the opcode.
|
|
*/
|
|
private OPCODE(int value) {
|
|
this.value = (byte)value;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the byte value of this opcode.
|
|
* @return The byte value of this opcode.
|
|
*/
|
|
public byte getValue() {
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the symbolic name of an opcode byte.
|
|
* @param value The byte value of the opcode.
|
|
* @return The symbolic opcode or null.
|
|
* @throws IllegalArgumentException If the byte value is not in the
|
|
* range 0..15.
|
|
*/
|
|
public static OPCODE getOpcode(int value) {
|
|
if (value < 0 || value > 15) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
return INVERSE_LUT[value];
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* The DNS message id.
|
|
*/
|
|
protected int id;
|
|
|
|
/**
|
|
* The DNS message opcode.
|
|
*/
|
|
protected OPCODE opcode;
|
|
|
|
/**
|
|
* The response code of this dns message.
|
|
*/
|
|
protected RESPONSE_CODE responseCode;
|
|
|
|
/**
|
|
* True if this is a query.
|
|
*/
|
|
protected boolean query;
|
|
|
|
/**
|
|
* True if this is a authorative response.
|
|
*/
|
|
protected boolean authoritativeAnswer;
|
|
|
|
/**
|
|
* True on truncate, tcp should be used.
|
|
*/
|
|
protected boolean truncated;
|
|
|
|
/**
|
|
* True if the server should recurse.
|
|
*/
|
|
protected boolean recursionDesired;
|
|
|
|
/**
|
|
* True if recursion is possible.
|
|
*/
|
|
protected boolean recursionAvailable;
|
|
|
|
/**
|
|
* True if the server regarded the response as authentic.
|
|
*/
|
|
protected boolean authenticData;
|
|
|
|
/**
|
|
* True if the server should not check the replies.
|
|
*/
|
|
protected boolean checkDisabled;
|
|
|
|
/**
|
|
* The question section content.
|
|
*/
|
|
protected Question questions[];
|
|
|
|
/**
|
|
* The answers section content.
|
|
*/
|
|
protected Record answers[];
|
|
|
|
/**
|
|
* The nameserver records.
|
|
*/
|
|
protected Record nameserverRecords[];
|
|
|
|
/**
|
|
* Additional resousrce records.
|
|
*/
|
|
protected Record additionalResourceRecords[];
|
|
|
|
/**
|
|
* The receive timestamp of this message.
|
|
*/
|
|
protected long receiveTimestamp;
|
|
|
|
/**
|
|
* Retrieve the current DNS message id.
|
|
* @return The current DNS message id.
|
|
*/
|
|
public int getId() {
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Set the current DNS message id.
|
|
* @param id The new DNS message id.
|
|
*/
|
|
public void setId(int id) {
|
|
this.id = id & 0xffff;
|
|
}
|
|
|
|
/**
|
|
* Get the receive timestamp if this message was created via parse.
|
|
* This should be used to evaluate TTLs.
|
|
* @return The receive timestamp in milliseconds.
|
|
*/
|
|
public long getReceiveTimestamp() {
|
|
return receiveTimestamp;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the query type (true or false;
|
|
* @return True if this DNS message is a query.
|
|
*/
|
|
public boolean isQuery() {
|
|
return query;
|
|
}
|
|
|
|
/**
|
|
* Set the query status of this message.
|
|
* @param query The new query status.
|
|
*/
|
|
public void setQuery(boolean query) {
|
|
this.query = query;
|
|
}
|
|
|
|
/**
|
|
* True if the DNS message is an authoritative answer.
|
|
* @return True if this an authoritative DNS message.
|
|
*/
|
|
public boolean isAuthoritativeAnswer() {
|
|
return authoritativeAnswer;
|
|
}
|
|
|
|
/**
|
|
* Set the authoritative answer flag.
|
|
* @param authoritativeAnswer Tge new authoritative answer value.
|
|
*/
|
|
public void setAuthoritativeAnswer(boolean authoritativeAnswer) {
|
|
this.authoritativeAnswer = authoritativeAnswer;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the truncation status of this message. True means that the
|
|
* client should try a tcp lookup.
|
|
* @return True if this message was truncated.
|
|
*/
|
|
public boolean isTruncated() {
|
|
return truncated;
|
|
}
|
|
|
|
/**
|
|
* Set the truncation bit on this DNS message.
|
|
* @param truncated The new truncated bit status.
|
|
*/
|
|
public void setTruncated(boolean truncated) {
|
|
this.truncated = truncated;
|
|
}
|
|
|
|
/**
|
|
* Check if this message preferes recursion.
|
|
* @return True if recursion is desired.
|
|
*/
|
|
public boolean isRecursionDesired() {
|
|
return recursionDesired;
|
|
}
|
|
|
|
/**
|
|
* Set the recursion desired flag on this message.
|
|
* @param recursionDesired The new recusrion setting.
|
|
*/
|
|
public void setRecursionDesired(boolean recursionDesired) {
|
|
this.recursionDesired = recursionDesired;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the recursion available flag of this DNS message.
|
|
* @return The recursion available flag of this message.
|
|
*/
|
|
public boolean isRecursionAvailable() {
|
|
return recursionAvailable;
|
|
}
|
|
|
|
/**
|
|
* Set the recursion available flog from this DNS message.
|
|
* @param recursionAvailable The new recursion available status.
|
|
*/
|
|
public void setRecursionAvailable(boolean recursionAvailable) {
|
|
this.recursionAvailable = recursionAvailable;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the authentic data flag of this message.
|
|
* @return The authentic data flag.
|
|
*/
|
|
public boolean isAuthenticData() {
|
|
return authenticData;
|
|
}
|
|
|
|
/**
|
|
* Set the authentic data flag on this DNS message.
|
|
* @param authenticData The new authentic data flag value.
|
|
*/
|
|
public void setAuthenticData(boolean authenticData) {
|
|
this.authenticData = authenticData;
|
|
}
|
|
|
|
/**
|
|
* Check if checks are disabled.
|
|
* @return The status of the CheckDisabled flag.
|
|
*/
|
|
public boolean isCheckDisabled() {
|
|
return checkDisabled;
|
|
}
|
|
|
|
/**
|
|
* Change the check status of this packet.
|
|
* @param checkDisabled The new check disabled value.
|
|
*/
|
|
public void setCheckDisabled(boolean checkDisabled) {
|
|
this.checkDisabled = checkDisabled;
|
|
}
|
|
|
|
/**
|
|
* Generate a binary dns packet out of this message.
|
|
* @return byte[] the binary representation.
|
|
* @throws IOException Should never happen.
|
|
*/
|
|
public byte[] toArray() throws IOException {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
|
DataOutputStream dos = new DataOutputStream(baos);
|
|
int header = 0;
|
|
if (query) {
|
|
header += 1 << 15;
|
|
}
|
|
if (opcode != null) {
|
|
header += opcode.getValue() << 11;
|
|
}
|
|
if (authoritativeAnswer) {
|
|
header += 1 << 10;
|
|
}
|
|
if (truncated) {
|
|
header += 1 << 9;
|
|
}
|
|
if (recursionDesired) {
|
|
header += 1 << 8;
|
|
}
|
|
if (recursionAvailable) {
|
|
header += 1 << 7;
|
|
}
|
|
if (authenticData) {
|
|
header += 1 << 5;
|
|
}
|
|
if (checkDisabled) {
|
|
header += 1 << 4;
|
|
}
|
|
if (responseCode != null) {
|
|
header += responseCode.getValue();
|
|
}
|
|
dos.writeShort((short)id);
|
|
dos.writeShort((short)header);
|
|
if (questions == null) {
|
|
dos.writeShort(0);
|
|
} else {
|
|
dos.writeShort((short)questions.length);
|
|
}
|
|
if (answers == null) {
|
|
dos.writeShort(0);
|
|
} else {
|
|
dos.writeShort((short)answers.length);
|
|
}
|
|
if (nameserverRecords == null) {
|
|
dos.writeShort(0);
|
|
} else {
|
|
dos.writeShort((short)nameserverRecords.length);
|
|
}
|
|
if (additionalResourceRecords == null) {
|
|
dos.writeShort(0);
|
|
} else {
|
|
dos.writeShort((short)additionalResourceRecords.length);
|
|
}
|
|
for (Question question: questions) {
|
|
dos.write(question.toByteArray());
|
|
}
|
|
dos.flush();
|
|
return baos.toByteArray();
|
|
}
|
|
|
|
/**
|
|
* Build a DNS Message based on a binary DNS message.
|
|
* @param data The DNS message data.
|
|
* @return Parsed DNSMessage message.
|
|
* @throws IOException On read errors.
|
|
*/
|
|
public static DNSMessage parse(byte data[]) throws IOException {
|
|
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
|
DataInputStream dis = new DataInputStream(bis);
|
|
DNSMessage message = new DNSMessage();
|
|
message.id = dis.readUnsignedShort();
|
|
int header = dis.readUnsignedShort();
|
|
message.query = ((header >> 15) & 1) == 0;
|
|
message.opcode = OPCODE.getOpcode((header >> 11) & 0xf);
|
|
message.authoritativeAnswer = ((header >> 10) & 1) == 1;
|
|
message.truncated = ((header >> 9) & 1) == 1;
|
|
message.recursionDesired = ((header >> 8) & 1) == 1;
|
|
message.recursionAvailable = ((header >> 7) & 1) == 1;
|
|
message.authenticData = ((header >> 5) & 1) == 1;
|
|
message.checkDisabled = ((header >> 4) & 1) == 1;
|
|
message.responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);
|
|
message.receiveTimestamp = System.currentTimeMillis();
|
|
int questionCount = dis.readUnsignedShort();
|
|
int answerCount = dis.readUnsignedShort();
|
|
int nameserverCount = dis.readUnsignedShort();
|
|
int additionalResourceRecordCount = dis.readUnsignedShort();
|
|
message.questions = new Question[questionCount];
|
|
while (questionCount-- > 0) {
|
|
Question q = Question.parse(dis, data);
|
|
message.questions[questionCount] = q;
|
|
}
|
|
message.answers = new Record[answerCount];
|
|
while (answerCount-- > 0) {
|
|
Record rr = new Record();
|
|
rr.parse(dis, data);
|
|
message.answers[answerCount] = rr;
|
|
}
|
|
message.nameserverRecords = new Record[nameserverCount];
|
|
while (nameserverCount-- > 0) {
|
|
Record rr = new Record();
|
|
rr.parse(dis, data);
|
|
message.nameserverRecords[nameserverCount] = rr;
|
|
}
|
|
message.additionalResourceRecords =
|
|
new Record[additionalResourceRecordCount];
|
|
while (additionalResourceRecordCount-- > 0) {
|
|
Record rr = new Record();
|
|
rr.parse(dis, data);
|
|
message.additionalResourceRecords[additionalResourceRecordCount] =
|
|
rr;
|
|
}
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Set the question part of this message.
|
|
* @param questions The questions.
|
|
*/
|
|
public void setQuestions(Question ... questions) {
|
|
this.questions = questions;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the opcode of this message.
|
|
* @return The opcode of this message.
|
|
*/
|
|
public OPCODE getOpcode() {
|
|
return opcode;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the response code of this message.
|
|
* @return The response code.
|
|
*/
|
|
public RESPONSE_CODE getResponseCode() {
|
|
return responseCode;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the question section of this message.
|
|
* @return The DNS question section.
|
|
*/
|
|
public Question[] getQuestions() {
|
|
return questions;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the answer records of this DNS message.
|
|
* @return The answer section of this DNS message.
|
|
*/
|
|
public Record[] getAnswers() {
|
|
return answers;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the nameserver records of this DNS message.
|
|
* @return The nameserver section of this DNS message.
|
|
*/
|
|
public Record[] getNameserverRecords() {
|
|
return nameserverRecords;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the additional resource records attached to this DNS message.
|
|
* @return The additional resource record section of this DNS message.
|
|
*/
|
|
public Record[] getAdditionalResourceRecords() {
|
|
return additionalResourceRecords;
|
|
}
|
|
|
|
public String toString() {
|
|
return "-- DNSMessage " + id + " --\n" +
|
|
"Q" + Arrays.toString(questions) +
|
|
"NS" + Arrays.toString(nameserverRecords) +
|
|
"A" + Arrays.toString(answers) +
|
|
"ARR" + Arrays.toString(additionalResourceRecords);
|
|
}
|
|
|
|
}
|