1package de.measite.minidns;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.DataInputStream;
6import java.io.DataOutputStream;
7import java.io.IOException;
8import java.util.Arrays;
9
10/**
11 * A DNS message as defined by rfc1035. The message consists of a header and
12 * 4 sections: question, answer, nameserver and addition resource record
13 * section.
14 * A message can either be parsed ({@see #parse(byte[])}) or serialized
15 * ({@see #toArray()}).
16 */
17public class DNSMessage {
18
19 /**
20 * Possible DNS reply codes.
21 */
22 public static enum RESPONSE_CODE {
23 NO_ERROR(0), FORMAT_ERR(1), SERVER_FAIL(2), NX_DOMAIN(3),
24 NO_IMP(4), REFUSED(5), YXDOMAIN(6), YXRRSET(7),
25 NXRRSET(8), NOT_AUTH(9),NOT_ZONE(10);
26
27 /**
28 * Reverse lookup table for response codes.
29 */
30 private final static RESPONSE_CODE INVERSE_LUT[] = new RESPONSE_CODE[]{
31 NO_ERROR, FORMAT_ERR, SERVER_FAIL, NX_DOMAIN, NO_IMP,
32 REFUSED, YXDOMAIN, YXRRSET, NXRRSET, NOT_AUTH, NOT_ZONE,
33 null, null, null, null, null
34 };
35
36 /**
37 * The response code value.
38 */
39 private final byte value;
40
41 /**
42 * Create a new response code.
43 * @param value The response code value.
44 */
45 private RESPONSE_CODE(int value) {
46 this.value = (byte)value;
47 }
48
49 /**
50 * Retrieve the byte value of the response code.
51 * @return
52 */
53 public byte getValue() {
54 return (byte) value;
55 }
56
57 /**
58 * Retrieve the response code for a byte value.
59 * @param value The byte value.
60 * @return The symbolic response code or null.
61 * @throws IllegalArgumentException if the value is not in the range of
62 * 0..15.
63 */
64 public static RESPONSE_CODE getResponseCode(int value) {
65 if (value < 0 || value > 15) {
66 throw new IllegalArgumentException();
67 }
68 return INVERSE_LUT[value];
69 }
70
71 };
72
73 /**
74 * Symbolic DNS Opcode values.
75 */
76 public static enum OPCODE {
77 QUERY(0),
78 INVERSE_QUERY(1),
79 STATUS(2),
80 NOTIFY(4),
81 UPDATE(5);
82
83 /**
84 * Lookup table for for obcode reolution.
85 */
86 private final static OPCODE INVERSE_LUT[] = new OPCODE[]{
87 QUERY, INVERSE_QUERY, STATUS, null, NOTIFY, UPDATE, null,
88 null, null, null, null, null, null, null, null
89 };
90
91 /**
92 * The value of this opcode.
93 */
94 private final byte value;
95
96 /**
97 * Create a new opcode for a given byte value.
98 * @param value The byte value of the opcode.
99 */
100 private OPCODE(int value) {
101 this.value = (byte)value;
102 }
103
104 /**
105 * Retrieve the byte value of this opcode.
106 * @return The byte value of this opcode.
107 */
108 public byte getValue() {
109 return value;
110 }
111
112 /**
113 * Retrieve the symbolic name of an opcode byte.
114 * @param value The byte value of the opcode.
115 * @return The symbolic opcode or null.
116 * @throws IllegalArgumentException If the byte value is not in the
117 * range 0..15.
118 */
119 public static OPCODE getOpcode(int value) {
120 if (value < 0 || value > 15) {
121 throw new IllegalArgumentException();
122 }
123 return INVERSE_LUT[value];
124 }
125
126 };
127
128 /**
129 * The DNS message id.
130 */
131 protected int id;
132
133 /**
134 * The DNS message opcode.
135 */
136 protected OPCODE opcode;
137
138 /**
139 * The response code of this dns message.
140 */
141 protected RESPONSE_CODE responseCode;
142
143 /**
144 * True if this is a query.
145 */
146 protected boolean query;
147
148 /**
149 * True if this is a authorative response.
150 */
151 protected boolean authoritativeAnswer;
152
153 /**
154 * True on truncate, tcp should be used.
155 */
156 protected boolean truncated;
157
158 /**
159 * True if the server should recurse.
160 */
161 protected boolean recursionDesired;
162
163 /**
164 * True if recursion is possible.
165 */
166 protected boolean recursionAvailable;
167
168 /**
169 * True if the server regarded the response as authentic.
170 */
171 protected boolean authenticData;
172
173 /**
174 * True if the server should not check the replies.
175 */
176 protected boolean checkDisabled;
177
178 /**
179 * The question section content.
180 */
181 protected Question questions[];
182
183 /**
184 * The answers section content.
185 */
186 protected Record answers[];
187
188 /**
189 * The nameserver records.
190 */
191 protected Record nameserverRecords[];
192
193 /**
194 * Additional resousrce records.
195 */
196 protected Record additionalResourceRecords[];
197
198 /**
199 * The receive timestamp of this message.
200 */
201 protected long receiveTimestamp;
202
203 /**
204 * Retrieve the current DNS message id.
205 * @return The current DNS message id.
206 */
207 public int getId() {
208 return id;
209 }
210
211 /**
212 * Set the current DNS message id.
213 * @param id The new DNS message id.
214 */
215 public void setId(int id) {
216 this.id = id & 0xffff;
217 }
218
219 /**
220 * Get the receive timestamp if this message was created via parse.
221 * This should be used to evaluate TTLs.
222 */
223 public long getReceiveTimestamp() {
224 return receiveTimestamp;
225 }
226
227 /**
228 * Retrieve the query type (true or false;
229 * @return True if this DNS message is a query.
230 */
231 public boolean isQuery() {
232 return query;
233 }
234
235 /**
236 * Set the query status of this message.
237 * @param query The new query status.
238 */
239 public void setQuery(boolean query) {
240 this.query = query;
241 }
242
243 /**
244 * True if the DNS message is an authoritative answer.
245 * @return True if this an authoritative DNS message.
246 */
247 public boolean isAuthoritativeAnswer() {
248 return authoritativeAnswer;
249 }
250
251 /**
252 * Set the authoritative answer flag.
253 * @param authoritativeAnswer Tge new authoritative answer value.
254 */
255 public void setAuthoritativeAnswer(boolean authoritativeAnswer) {
256 this.authoritativeAnswer = authoritativeAnswer;
257 }
258
259 /**
260 * Retrieve the truncation status of this message. True means that the
261 * client should try a tcp lookup.
262 * @return True if this message was truncated.
263 */
264 public boolean isTruncated() {
265 return truncated;
266 }
267
268 /**
269 * Set the truncation bit on this DNS message.
270 * @param truncated The new truncated bit status.
271 */
272 public void setTruncated(boolean truncated) {
273 this.truncated = truncated;
274 }
275
276 /**
277 * Check if this message preferes recursion.
278 * @return True if recursion is desired.
279 */
280 public boolean isRecursionDesired() {
281 return recursionDesired;
282 }
283
284 /**
285 * Set the recursion desired flag on this message.
286 * @param recursionDesired The new recusrion setting.
287 */
288 public void setRecursionDesired(boolean recursionDesired) {
289 this.recursionDesired = recursionDesired;
290 }
291
292 /**
293 * Retrieve the recursion available flag of this DNS message.
294 * @return The recursion available flag of this message.
295 */
296 public boolean isRecursionAvailable() {
297 return recursionAvailable;
298 }
299
300 /**
301 * Set the recursion available flog from this DNS message.
302 * @param recursionAvailable The new recursion available status.
303 */
304 public void setRecursionAvailable(boolean recursionAvailable) {
305 this.recursionAvailable = recursionAvailable;
306 }
307
308 /**
309 * Retrieve the authentic data flag of this message.
310 * @return The authentic data flag.
311 */
312 public boolean isAuthenticData() {
313 return authenticData;
314 }
315
316 /**
317 * Set the authentic data flag on this DNS message.
318 * @param authenticData The new authentic data flag value.
319 */
320 public void setAuthenticData(boolean authenticData) {
321 this.authenticData = authenticData;
322 }
323
324 /**
325 * Check if checks are disabled.
326 * @return The status of the CheckDisabled flag.
327 */
328 public boolean isCheckDisabled() {
329 return checkDisabled;
330 }
331
332 /**
333 * Change the check status of this packet.
334 * @param checkDisabled
335 */
336 public void setCheckDisabled(boolean checkDisabled) {
337 this.checkDisabled = checkDisabled;
338 }
339
340 /**
341 * Generate a binary dns packet out of this message.
342 * @return byte[] the binary representation.
343 * @throws IOException Should never happen.
344 */
345 public byte[] toArray() throws IOException {
346 ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
347 DataOutputStream dos = new DataOutputStream(baos);
348 int header = 0;
349 if (query) {
350 header += 1 << 15;
351 }
352 if (opcode != null) {
353 header += opcode.getValue() << 11;
354 }
355 if (authoritativeAnswer) {
356 header += 1 << 10;
357 }
358 if (truncated) {
359 header += 1 << 9;
360 }
361 if (recursionDesired) {
362 header += 1 << 8;
363 }
364 if (recursionAvailable) {
365 header += 1 << 7;
366 }
367 if (authenticData) {
368 header += 1 << 5;
369 }
370 if (checkDisabled) {
371 header += 1 << 4;
372 }
373 if (responseCode != null) {
374 header += responseCode.getValue();
375 }
376 dos.writeShort((short)id);
377 dos.writeShort((short)header);
378 if (questions == null) {
379 dos.writeShort(0);
380 } else {
381 dos.writeShort((short)questions.length);
382 }
383 if (answers == null) {
384 dos.writeShort(0);
385 } else {
386 dos.writeShort((short)answers.length);
387 }
388 if (nameserverRecords == null) {
389 dos.writeShort(0);
390 } else {
391 dos.writeShort((short)nameserverRecords.length);
392 }
393 if (additionalResourceRecords == null) {
394 dos.writeShort(0);
395 } else {
396 dos.writeShort((short)additionalResourceRecords.length);
397 }
398 for (Question question: questions) {
399 dos.write(question.toByteArray());
400 }
401 dos.flush();
402 return baos.toByteArray();
403 }
404
405 /**
406 * Build a DNS Message based on a binary DNS message.
407 * @param data The DNS message data.
408 * @return Parsed DNSMessage message.
409 * @throws IOException On read errors.
410 */
411 public static DNSMessage parse(byte data[]) throws IOException {
412 ByteArrayInputStream bis = new ByteArrayInputStream(data);
413 DataInputStream dis = new DataInputStream(bis);
414 DNSMessage message = new DNSMessage();
415 message.id = dis.readUnsignedShort();
416 int header = dis.readUnsignedShort();
417 message.query = ((header >> 15) & 1) == 0;
418 message.opcode = OPCODE.getOpcode((header >> 11) & 0xf);
419 message.authoritativeAnswer = ((header >> 10) & 1) == 1;
420 message.truncated = ((header >> 9) & 1) == 1;
421 message.recursionDesired = ((header >> 8) & 1) == 1;
422 message.recursionAvailable = ((header >> 7) & 1) == 1;
423 message.authenticData = ((header >> 5) & 1) == 1;
424 message.checkDisabled = ((header >> 4) & 1) == 1;
425 message.responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);
426 message.receiveTimestamp = System.currentTimeMillis();
427 int questionCount = dis.readUnsignedShort();
428 int answerCount = dis.readUnsignedShort();
429 int nameserverCount = dis.readUnsignedShort();
430 int additionalResourceRecordCount = dis.readUnsignedShort();
431 message.questions = new Question[questionCount];
432 while (questionCount-- > 0) {
433 Question q = Question.parse(dis, data);
434 message.questions[questionCount] = q;
435 }
436 message.answers = new Record[answerCount];
437 while (answerCount-- > 0) {
438 Record rr = new Record();
439 rr.parse(dis, data);
440 message.answers[answerCount] = rr;
441 }
442 message.nameserverRecords = new Record[nameserverCount];
443 while (nameserverCount-- > 0) {
444 Record rr = new Record();
445 rr.parse(dis, data);
446 message.nameserverRecords[nameserverCount] = rr;
447 }
448 message.additionalResourceRecords =
449 new Record[additionalResourceRecordCount];
450 while (additionalResourceRecordCount-- > 0) {
451 Record rr = new Record();
452 rr.parse(dis, data);
453 message.additionalResourceRecords[additionalResourceRecordCount] =
454 rr;
455 }
456 return message;
457 }
458
459 /**
460 * Set the question part of this message.
461 * @param questions The questions.
462 */
463 public void setQuestions(Question ... questions) {
464 this.questions = questions;
465 }
466
467 /**
468 * Retrieve the opcode of this message.
469 * @return The opcode of this message.
470 */
471 public OPCODE getOpcode() {
472 return opcode;
473 }
474
475 /**
476 * Retrieve the response code of this message.
477 * @return The response code.
478 */
479 public RESPONSE_CODE getResponseCode() {
480 return responseCode;
481 }
482
483 /**
484 * Retrieve the question section of this message.
485 * @return The DNS question section.
486 */
487 public Question[] getQuestions() {
488 return questions;
489 }
490
491 /**
492 * Retrieve the answer records of this DNS message.
493 * @return The answer section of this DNS message.
494 */
495 public Record[] getAnswers() {
496 return answers;
497 }
498
499 /**
500 * Retrieve the nameserver records of this DNS message.
501 * @return The nameserver section of this DNS message.
502 */
503 public Record[] getNameserverRecords() {
504 return nameserverRecords;
505 }
506
507 /**
508 * Retrieve the additional resource records attached to this DNS message.
509 * @return The additional resource record section of this DNS message.
510 */
511 public Record[] getAdditionalResourceRecords() {
512 return additionalResourceRecords;
513 }
514
515 public String toString() {
516 return "-- DNSMessage " + id + " --\n" +
517 Arrays.toString(answers);
518 }
519
520}